@ -22,6 +22,7 @@
from __future__ import ( absolute_import , division , print_function )
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
__metaclass__ = type
import sys
import os
import os
import shlex
import shlex
import shutil
import shutil
@ -35,7 +36,10 @@ from hashlib import sha256
from hashlib import md5
from hashlib import md5
from binascii import hexlify
from binascii import hexlify
from binascii import unhexlify
from binascii import unhexlify
from six import binary_type , byte2int , PY2 , text_type
from ansible import constants as C
from ansible import constants as C
from ansible . utils . unicode import to_unicode , to_bytes
try :
try :
from Crypto . Hash import SHA256 , HMAC
from Crypto . Hash import SHA256 , HMAC
@ -66,7 +70,7 @@ except ImportError:
CRYPTO_UPGRADE = " ansible-vault requires a newer version of pycrypto than the one installed on your platform. You may fix this with OS-specific commands such as: yum install python-devel; rpm -e --nodeps python-crypto; pip install pycrypto "
CRYPTO_UPGRADE = " ansible-vault requires a newer version of pycrypto than the one installed on your platform. You may fix this with OS-specific commands such as: yum install python-devel; rpm -e --nodeps python-crypto; pip install pycrypto "
HEADER = ' $ANSIBLE_VAULT '
HEADER = u ' $ANSIBLE_VAULT '
CIPHER_WHITELIST = [ ' AES ' , ' AES256 ' ]
CIPHER_WHITELIST = [ ' AES ' , ' AES256 ' ]
class VaultLib ( object ) :
class VaultLib ( object ) :
@ -77,25 +81,27 @@ class VaultLib(object):
self . version = ' 1.1 '
self . version = ' 1.1 '
def is_encrypted ( self , data ) :
def is_encrypted ( self , data ) :
data = to_unicode ( data )
if data . startswith ( HEADER ) :
if data . startswith ( HEADER ) :
return True
return True
else :
else :
return False
return False
def encrypt ( self , data ) :
def encrypt ( self , data ) :
data = to_unicode ( data )
if self . is_encrypted ( data ) :
if self . is_encrypted ( data ) :
raise errors . AnsibleError ( " data is already encrypted " )
raise errors . AnsibleError ( " data is already encrypted " )
if not self . cipher_name :
if not self . cipher_name :
self . cipher_name = " AES256 "
self . cipher_name = " AES256 "
# raise errors.AnsibleError("the cipher must be set before encrypting data")
# raise errors.AnsibleError("the cipher must be set before encrypting data")
if ' Vault ' + self . cipher_name in globals ( ) and self . cipher_name in CIPHER_WHITELIST :
if ' Vault ' + self . cipher_name in globals ( ) and self . cipher_name in CIPHER_WHITELIST :
cipher = globals ( ) [ ' Vault ' + self . cipher_name ]
cipher = globals ( ) [ ' Vault ' + self . cipher_name ]
this_cipher = cipher ( )
this_cipher = cipher ( )
else :
else :
raise errors . AnsibleError ( " %s cipher could not be found " % self . cipher_name )
raise errors . AnsibleError ( " {} cipher could not be found " . format ( self . cipher_name ) )
"""
"""
# combine sha + data
# combine sha + data
@ -111,6 +117,8 @@ class VaultLib(object):
return tmp_data
return tmp_data
def decrypt ( self , data ) :
def decrypt ( self , data ) :
data = to_bytes ( data )
if self . password is None :
if self . password is None :
raise errors . AnsibleError ( " A vault password must be specified to decrypt data " )
raise errors . AnsibleError ( " A vault password must be specified to decrypt data " )
@ -121,11 +129,12 @@ class VaultLib(object):
data = self . _split_header ( data )
data = self . _split_header ( data )
# create the cipher object
# create the cipher object
if ' Vault ' + self . cipher_name in globals ( ) and self . cipher_name in CIPHER_WHITELIST :
ciphername = to_unicode ( self . cipher_name )
cipher = globals ( ) [ ' Vault ' + self . cipher_name ]
if ' Vault ' + ciphername in globals ( ) and ciphername in CIPHER_WHITELIST :
cipher = globals ( ) [ ' Vault ' + ciphername ]
this_cipher = cipher ( )
this_cipher = cipher ( )
else :
else :
raise errors . AnsibleError ( " %s cipher could not be found " % self . cipher_name )
raise errors . AnsibleError ( " {} cipher could not be found " . format ( ciphername ) )
# try to unencrypt data
# try to unencrypt data
data = this_cipher . decrypt ( data , self . password )
data = this_cipher . decrypt ( data , self . password )
@ -138,15 +147,13 @@ class VaultLib(object):
# combine header and encrypted data in 80 char columns
# combine header and encrypted data in 80 char columns
#tmpdata = hexlify(data)
#tmpdata = hexlify(data)
tmpdata = [ data [ i : i + 80 ] for i in range ( 0 , len ( data ) , 80 ) ]
tmpdata = [ to_bytes ( data [ i : i + 80 ] ) for i in range ( 0 , len ( data ) , 80 ) ]
if not self . cipher_name :
if not self . cipher_name :
raise errors . AnsibleError ( " the cipher must be set before adding a header " )
raise errors . AnsibleError ( " the cipher must be set before adding a header " )
dirty_data = HEADER + " ; " + str ( self . version ) + " ; " + self . cipher_name + " \n "
dirty_data = to_bytes ( HEADER + " ; " + self . version + " ; " + self . cipher_name + " \n " )
for l in tmpdata :
for l in tmpdata :
dirty_data + = l + ' \n '
dirty_data + = l + b ' \n '
return dirty_data
return dirty_data
@ -154,12 +161,12 @@ class VaultLib(object):
def _split_header ( self , data ) :
def _split_header ( self , data ) :
# used by decrypt
# used by decrypt
tmpdata = data . split ( ' \n ' )
tmpdata = data . split ( b ' \n ' )
tmpheader = tmpdata [ 0 ] . strip ( ) . split ( ' ; ' )
tmpheader = tmpdata [ 0 ] . strip ( ) . split ( b ' ; ' )
self . version = str ( tmpheader [ 1 ] . strip ( ) )
self . version = to_unicode ( tmpheader [ 1 ] . strip ( ) )
self . cipher_name = str ( tmpheader [ 2 ] . strip ( ) )
self . cipher_name = to_unicode ( tmpheader [ 2 ] . strip ( ) )
clean_data = ' \n ' . join ( tmpdata [ 1 : ] )
clean_data = b ' \n ' . join ( tmpdata [ 1 : ] )
"""
"""
# strip out newline, join, unhex
# strip out newline, join, unhex
@ -369,9 +376,10 @@ class VaultAES(object):
""" Create a key and an initialization vector """
""" Create a key and an initialization vector """
d = d_i = ' '
d = d_i = b ' '
while len ( d ) < key_length + iv_length :
while len ( d ) < key_length + iv_length :
d_i = md5 ( d_i + password + salt ) . digest ( )
text = " {} {} {} " . format ( d_i , password , salt )
d_i = md5 ( to_bytes ( text ) ) . digest ( )
d + = d_i
d + = d_i
key = d [ : key_length ]
key = d [ : key_length ]
@ -385,10 +393,10 @@ class VaultAES(object):
# combine sha + data
# combine sha + data
this_sha = sha256 ( data) . hexdigest ( )
this_sha = sha256 ( to_bytes( data) ) . hexdigest ( )
tmp_data = this_sha + " \n " + data
tmp_data = this_sha + " \n " + data
in_file = BytesIO ( t mp_data)
in_file = BytesIO ( t o_bytes( t mp_data) )
in_file . seek ( 0 )
in_file . seek ( 0 )
out_file = BytesIO ( )
out_file = BytesIO ( )
@ -400,20 +408,24 @@ class VaultAES(object):
key , iv = self . aes_derive_key_and_iv ( password , salt , key_length , bs )
key , iv = self . aes_derive_key_and_iv ( password , salt , key_length , bs )
cipher = AES . new ( key , AES . MODE_CBC , iv )
cipher = AES . new ( key , AES . MODE_CBC , iv )
out_file . write ( ' Salted__ ' + salt )
full = to_bytes ( b ' Salted__ ' + salt )
out_file . write ( full )
print ( repr ( full ) )
finished = False
finished = False
while not finished :
while not finished :
chunk = in_file . read ( 1024 * bs )
chunk = in_file . read ( 1024 * bs )
if len ( chunk ) == 0 or len ( chunk ) % bs != 0 :
if len ( chunk ) == 0 or len ( chunk ) % bs != 0 :
padding_length = ( bs - len ( chunk ) % bs ) or bs
padding_length = ( bs - len ( chunk ) % bs ) or bs
chunk + = padding_length * chr ( padding_length )
chunk + = to_bytes( padding_length * chr ( padding_length ) )
finished = True
finished = True
out_file . write ( cipher . encrypt ( chunk ) )
out_file . write ( cipher . encrypt ( chunk ) )
out_file . seek ( 0 )
out_file . seek ( 0 )
enc_data = out_file . read ( )
enc_data = out_file . read ( )
#print(enc_data)
tmp_data = hexlify ( enc_data )
tmp_data = hexlify ( enc_data )
assert isinstance ( tmp_data , binary_type )
return tmp_data
return tmp_data
@ -423,7 +435,7 @@ class VaultAES(object):
# http://stackoverflow.com/a/14989032
# http://stackoverflow.com/a/14989032
data = ' ' . join ( data . split ( ' \n ' ) )
data = b ' ' . join ( data . split ( b ' \n ' ) )
data = unhexlify ( data )
data = unhexlify ( data )
in_file = BytesIO ( data )
in_file = BytesIO ( data )
@ -431,29 +443,35 @@ class VaultAES(object):
out_file = BytesIO ( )
out_file = BytesIO ( )
bs = AES . block_size
bs = AES . block_size
salt = in_file . read ( bs ) [ len ( ' Salted__ ' ) : ]
tmpsalt = in_file . read ( bs )
print ( repr ( tmpsalt ) )
salt = tmpsalt [ len ( ' Salted__ ' ) : ]
key , iv = self . aes_derive_key_and_iv ( password , salt , key_length , bs )
key , iv = self . aes_derive_key_and_iv ( password , salt , key_length , bs )
cipher = AES . new ( key , AES . MODE_CBC , iv )
cipher = AES . new ( key , AES . MODE_CBC , iv )
next_chunk = ' '
next_chunk = b ' '
finished = False
finished = False
while not finished :
while not finished :
chunk , next_chunk = next_chunk , cipher . decrypt ( in_file . read ( 1024 * bs ) )
chunk , next_chunk = next_chunk , cipher . decrypt ( in_file . read ( 1024 * bs ) )
if len ( next_chunk ) == 0 :
if len ( next_chunk ) == 0 :
if PY2 :
padding_length = ord ( chunk [ - 1 ] )
padding_length = ord ( chunk [ - 1 ] )
else :
padding_length = chunk [ - 1 ]
chunk = chunk [ : - padding_length ]
chunk = chunk [ : - padding_length ]
finished = True
finished = True
out_file . write ( chunk )
out_file . write ( chunk )
# reset the stream pointer to the beginning
# reset the stream pointer to the beginning
out_file . seek ( 0 )
out_file . seek ( 0 )
new_data = out_file. read ( )
new_data = to_unicode( out_file. read ( ) )
# split out sha and verify decryption
# split out sha and verify decryption
split_data = new_data . split ( " \n " )
split_data = new_data . split ( " \n " )
this_sha = split_data [ 0 ]
this_sha = split_data [ 0 ]
this_data = ' \n ' . join ( split_data [ 1 : ] )
this_data = ' \n ' . join ( split_data [ 1 : ] )
test_sha = sha256 ( t his_data) . hexdigest ( )
test_sha = sha256 ( t o_bytes( t his_data) ) . hexdigest ( )
if this_sha != test_sha :
if this_sha != test_sha :
raise errors . AnsibleError ( " Decryption failed " )
raise errors . AnsibleError ( " Decryption failed " )
@ -527,16 +545,16 @@ class VaultAES256(object):
# COMBINE SALT, DIGEST AND DATA
# COMBINE SALT, DIGEST AND DATA
hmac = HMAC . new ( key2 , cryptedData , SHA256 )
hmac = HMAC . new ( key2 , cryptedData , SHA256 )
message = " %s \n %s \n %s " % ( hexlify ( salt ) , hmac . hexdigest ( ) , hexlify ( cryptedData ) )
message = b' ' . join ( [ hexlify ( salt ) , b " \n " , to_bytes ( hmac . hexdigest ( ) ), b " \n " , hexlify ( cryptedData ) ] )
message = hexlify ( message )
message = hexlify ( message )
return message
return message
def decrypt ( self , data , password ) :
def decrypt ( self , data , password ) :
# SPLIT SALT, DIGEST, AND DATA
# SPLIT SALT, DIGEST, AND DATA
data = ' ' . join ( data . split ( " \n " ) )
data = b ' ' . join ( data . split ( b " \n " ) )
data = unhexlify ( data )
data = unhexlify ( data )
salt , cryptedHmac , cryptedData = data . split ( " \n " , 2 )
salt , cryptedHmac , cryptedData = data . split ( b " \n " , 2 )
salt = unhexlify ( salt )
salt = unhexlify ( salt )
cryptedData = unhexlify ( cryptedData )
cryptedData = unhexlify ( cryptedData )
@ -544,7 +562,7 @@ class VaultAES256(object):
# EXIT EARLY IF DIGEST DOESN'T MATCH
# EXIT EARLY IF DIGEST DOESN'T MATCH
hmacDecrypt = HMAC . new ( key2 , cryptedData , SHA256 )
hmacDecrypt = HMAC . new ( key2 , cryptedData , SHA256 )
if not self . is_equal ( cryptedHmac , hmacDecrypt. hexdigest ( ) ) :
if not self . is_equal ( cryptedHmac , to_bytes( hmacDecrypt. hexdigest ( ) ) ) :
return None
return None
# SET THE COUNTER AND THE CIPHER
# SET THE COUNTER AND THE CIPHER
@ -555,19 +573,31 @@ class VaultAES256(object):
decryptedData = cipher . decrypt ( cryptedData )
decryptedData = cipher . decrypt ( cryptedData )
# UNPAD DATA
# UNPAD DATA
try :
padding_length = ord ( decryptedData [ - 1 ] )
padding_length = ord ( decryptedData [ - 1 ] )
except TypeError :
padding_length = decryptedData [ - 1 ]
decryptedData = decryptedData [ : - padding_length ]
decryptedData = decryptedData [ : - padding_length ]
return decryptedData
return to_unicode( decryptedData)
def is_equal ( self , a , b ) :
def is_equal ( self , a , b ) :
"""
Comparing 2 byte arrrays in constant time
to avoid timing attacks .
It would be nice if there was a library for this but
hey .
"""
# http://codahale.com/a-lesson-in-timing-attacks/
# http://codahale.com/a-lesson-in-timing-attacks/
if len ( a ) != len ( b ) :
if len ( a ) != len ( b ) :
return False
return False
result = 0
result = 0
for x , y in zip ( a , b ) :
for x , y in zip ( a , b ) :
if PY2 :
result | = ord ( x ) ^ ord ( y )
result | = ord ( x ) ^ ord ( y )
else :
result | = x ^ y
return result == 0
return result == 0