, "NaCl_LEN" : 16 })
, "enc": "aes-gcm" } }
+# Mode zero is unencrypted and only provided for testing purposes. nless
+# the encryptor / decryptor are explicitly instructed to do so.
+MIN_SECURE_PARAMETERS = 1
+
###############################################################################
## constants
###############################################################################
strict_ivs = False # if True, panic on duplicate object IV
password = None
paramversion = None
+ insecure = False # allow plaintext parameters
stats = { "in" : 0
, "out" : 0
, "obj" : 0 }
def set_parameters (self, password=None, key=None, paramversion=None,
- nacl=None, counter=None, strict_ivs=False):
+ nacl=None, counter=None, strict_ivs=False,
+ insecure=False):
"""
Configure the internal state of a crypto context. Not intended for
external use.
+
+ A parameter version indicating passthrough (plaintext) mode is rejected
+ with an ``InvalidParameter`` unless ``insecure`` is set.
"""
self.next_fixed ()
self.set_object_counter (counter)
self.strict_ivs = strict_ivs
+ self.insecure = insecure
+
if paramversion is not None:
+ if self.insecure is False \
+ and paramversion < MIN_SECURE_PARAMETERS:
+ raise InvalidParameter \
+ ("set_parameters: requested parameter version %d but "
+ "plaintext encryption disallowed in secure context!"
+ % paramversion)
self.paramversion = paramversion
if key is not None:
or self.password != password
or self.nacl != nacl):
self.set_parameters (password=password, paramversion=paramversion,
- nacl=nacl, strict_ivs=self.strict_ivs)
+ nacl=nacl, strict_ivs=self.strict_ivs,
+ insecure=self.insecure)
def check_duplicate_iv (self, iv):
paramenc = None
def __init__ (self, version, paramversion, password=None, key=None, nacl=None,
- counter=AES_GCM_IV_CNT_DATA, strict_ivs=True):
+ counter=AES_GCM_IV_CNT_DATA, strict_ivs=True, insecure=False):
"""
The ctor will throw immediately if one of the parameters does not conform
to our expectations.
``AES_GCM_IV_CNT_INDEX`` are unique in each backup set
and cannot be reused even with different fixed parts.
:type strict_ivs: bool
+ :type insecure: bool, whether to permit passthrough mode
"""
if password is None and key is None \
or password is not None and key is not None :
self.paramenc = ENCRYPTION_PARAMETERS.get (paramversion) ["enc"]
super().__init__ (password, key, paramversion, nacl, counter=counter,
- strict_ivs=strict_ivs)
+ strict_ivs=strict_ivs, insecure=insecure)
def next_fixed (self, retries=PDTCRYPT_IV_GEN_MAX_RETRIES):
hdr_ctsize = -1
def __init__ (self, password=None, key=None, counter=None, fixedparts=None,
- strict_ivs=False):
+ strict_ivs=False, insecure=False):
"""
Sanitizing ctor for the decryption context. ``fixedparts`` specifies a
list of IV fixed parts accepted during decryption. If a fixed part is
``AES_GCM_IV_CNT_INDEX`` are unique in each backup set
and cannot be reused even with different fixed parts.
:type fixedparts: bytes list
+ :type insecure: bool, whether to process objects encrypted in
+ passthrough mode (*``paramversion`` < 1*)
"""
if password is None and key is None \
or password is not None and key is not None :
self.fixed.sort ()
super().__init__ (password=password, key=key, counter=counter,
- strict_ivs=strict_ivs)
+ strict_ivs=strict_ivs, insecure=insecure)
def valid_fixed_part (self, iv):
TEST_VERSION = 1
TEST_PARAMVERSION = 1
TEST_STATIC_NACL = os.urandom (CRYPTO_NACL_SIZE)
+PLAIN_PARAMVERSION = 0
def faux_hdr (ctsize=1337, iv=None):
return \
password=password,
nacl=TEST_STATIC_NACL)
+ def test_crypto_aes_gcm_enc_ctor_bad_plainparams (self):
+ """Refuse plaintext passthrough mode by default."""
+ password = str (os.urandom (42))
+ with self.assertRaises (crypto.InvalidParameter):
+ encryptor = crypto.Encrypt (TEST_VERSION,
+ PLAIN_PARAMVERSION,
+ password=password,
+ nacl=TEST_STATIC_NACL)
+
+
+ def test_crypto_aes_gcm_enc_ctor_ok_insecure_plainparams (self):
+ """
+ Comply with request for plaintext passthrough mode if the
+ *insecure* flag is passed.
+ """
+ password = str (os.urandom (42))
+ encryptor = crypto.Encrypt (TEST_VERSION,
+ PLAIN_PARAMVERSION,
+ password=password,
+ nacl=TEST_STATIC_NACL,
+ insecure=True)
+
def test_crypto_aes_gcm_enc_ctor_key (self):
key = os.urandom (42)
assert plaintext == TEST_PLAINTEXT
+ def test_crypto_aes_gcm_dec_plain_bad (self):
+ """
+ Downgrade to plaintext must not be allowed in parameters
+ obtained from headers.
+ """
+ password = str (os.urandom (42))
+ encryptor = crypto.Encrypt (TEST_VERSION,
+ TEST_PARAMVERSION,
+ password=password,
+ nacl=TEST_STATIC_NACL)
+
+ header_dummy = encryptor.next (TEST_DUMMY_FILENAME)
+ _, ciphertext = encryptor.process (TEST_PLAINTEXT)
+ rest, header, fixed = encryptor.done (header_dummy)
+ ciphertext += rest
+
+ header = crypto.hdr_read (header)
+ header ["paramversion"] = PLAIN_PARAMVERSION
+ ok, header = crypto.hdr_make (header)
+ assert ok
+
+ decryptor = crypto.Decrypt (password=password, fixedparts=fixed)
+ with self.assertRaises (crypto.InvalidParameter):
+ decryptor.next (header)
+
+
+ def test_crypto_aes_gcm_dec_plain_ok_insecure (self):
+ """
+ Allow plaintext crypto mode if *insecure* flag is passed.
+ """
+ password = str (os.urandom (42))
+ encryptor = crypto.Encrypt (TEST_VERSION,
+ PLAIN_PARAMVERSION,
+ password=password,
+ nacl=TEST_STATIC_NACL,
+ insecure=True)
+
+ header_dummy = encryptor.next (TEST_DUMMY_FILENAME)
+ _, ciphertext = encryptor.process (TEST_PLAINTEXT)
+ rest, header, fixed = encryptor.done (header_dummy)
+ ciphertext += rest
+
+ header = crypto.hdr_read (header)
+ header ["paramversion"] = PLAIN_PARAMVERSION
+ ok, header = crypto.hdr_make (header)
+ assert ok
+
+ decryptor = crypto.Decrypt (password=password,
+ fixedparts=fixed,
+ insecure=True)
+ decryptor.next (header)
+ plaintext = decryptor.process (ciphertext)
+ rest = decryptor.done ()
+ plaintext += rest
+
+ assert plaintext == TEST_PLAINTEXT
+
+
def test_crypto_aes_gcm_dec_bad_tag (self):
password = str (os.urandom (42))
encryptor = crypto.Encrypt (TEST_VERSION,