rework crypto.py unittests for revised encryption
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Thu, 6 Apr 2017 15:56:09 +0000 (17:56 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Mon, 2 Apr 2018 11:34:08 +0000 (13:34 +0200)
Main changes:

    - Adjust usage to revised encryption handler.

    - Adapt to header format.

    - Adjust to changes in error passing (above all ``hdr_read()``).

    - Remove Scrypt or tag tests, these interfaces are no longer
      available.

testing/test_crypto.py

index 8f54387..c0d960b 100644 (file)
@@ -12,13 +12,16 @@ import cryptography
 def b(s):
     return s.encode("UTF-8")
 
-TEST_PLAINTEXT    = b("gentlemen don’t read each other’s mail")
-TEST_PASSPHRASE   = b"test1234"
-TEST_AES_GCM_AAD  = b"authenticated plain text"
-
-CRYPTO_NACL_SIZE  = 12
+CRYPTO_NACL_SIZE  = 16
 CRYPTO_KEY_SIZE   = 16
-CRYPTO_TAG_SIZE   = 16
+
+TEST_PLAINTEXT       = b("gentlemen don’t read each other’s mail")
+TEST_PASSPHRASE      = b"test1234"
+TEST_AES_GCM_AAD     = b"authenticated plain text"
+TEST_DUMMY_FILENAME  = "insurance-file.txt"
+TEST_VERSION         = 1
+TEST_PARAMVERSION    = 1
+TEST_STATIC_NACL     = os.urandom (CRYPTO_NACL_SIZE)
 
 def faux_hdr (ctsize=1337, iv=None):
     return \
@@ -29,6 +32,8 @@ def faux_hdr (ctsize=1337, iv=None):
         ,           "iv" : iv or binascii.unhexlify(b"0011223344556677"
                                                     b"8899aabb")
         ,       "ctsize" : ctsize
+        ,          "tag" : binascii.unhexlify(b"deadbeefbadb100d"
+                                              b"b1eedc0ffeedea15")
         }
 
 
@@ -51,227 +56,182 @@ class CryptoLayerTest (unittest.TestCase):
 
 class AESGCMTest (CryptoLayerTest):
 
-    def test_crypto_aes_gcm_enc_simple (self):
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-
-
-    def test_crypto_aes_gcm_enc_tag_retrieve (self):
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        ok, ct = enc.process_chunk (TEST_PLAINTEXT)
-        if ok is False or ct is None:
-            raise "error encrypting chunk [%s]" % TEST_PLAINTEXT
-        ok, ct, tag = enc.done ()
-        if ok is False or ct is None:
-            raise "error finalizing encryption"
-        if not tag:
-            raise "no tag received upon completing the encryption"
-
-
-    def test_crypto_aes_gcm_enc_tag_size (self):
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        ok, ct = enc.process_chunk (TEST_PLAINTEXT)
-        if ok is False or ct is None:
-            raise "error encrypting chunk [%s]" % TEST_PLAINTEXT
-        ok, ct, tag = enc.done ()
-        if ok is False or ct is None:
-            raise "error finalizing encryption"
-        if not tag:
-            raise "no tag received upon completing the encryption"
-        assert len (tag) == CRYPTO_TAG_SIZE
+    def test_crypto_aes_gcm_enc_ctor (self):
+        password   = str (os.urandom (42))
+        encryptor  = crypto.Encrypt (password, TEST_VERSION,
+                                     TEST_PARAMVERSION, nacl=TEST_STATIC_NACL)
+
+
+    def test_crypto_aes_gcm_enc_header_size (self):
+        password       = str (os.urandom (42))
+        encryptor      = crypto.Encrypt (password, TEST_VERSION,
+                                         TEST_PARAMVERSION,
+                                         nacl=TEST_STATIC_NACL)
+
+        header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
+        assert len (header_dummy) == crypto.PDTCRYPT_HDR_SIZE
+        _              = encryptor.process (TEST_PLAINTEXT)
+        _, header, _   = encryptor.done (header_dummy)
+        assert len (header) == crypto.PDTCRYPT_HDR_SIZE
 
 
     def test_crypto_aes_gcm_enc_chunk_size (self):
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        ok, ct = enc.process_chunk (TEST_PLAINTEXT)
-        if ok is False or ct is None:
-            raise "error encrypting chunk [%s]" % TEST_PLAINTEXT
-        assert len (ct) == len (TEST_PLAINTEXT)
-        ok, ct, tag = enc.done ()
-        if ok is False or ct is None:
-            raise "error finalizing encryption"
-        if not tag:
-            raise "no tag received upon completing the encryption"
-        assert len (ct) == 0
+        password       = str (os.urandom (42))
+        encryptor      = crypto.Encrypt (password, TEST_VERSION,
+                                         TEST_PARAMVERSION,
+                                         nacl=TEST_STATIC_NACL)
+
+        header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
+        ciphertext     = encryptor.process (TEST_PLAINTEXT)
+        assert len (ciphertext) == len (TEST_PLAINTEXT)
+        rest, header, fixed = encryptor.done (header_dummy)
+        assert len (rest) == 0
 
 
     def test_crypto_aes_gcm_dec_simple (self):
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        iv = enc.iv
-        dec = crypto.AES_GCM_context (crypto.DECRYPT, key, TEST_AES_GCM_AAD, iv = iv)
-        ok, ct = enc.process_chunk (TEST_PLAINTEXT)
-        ok, _, tag = enc.done ()
-        ok, pt = dec.process_chunk (ct)
-        ok, _, _ = dec.done (tag)
-        assert pt == TEST_PLAINTEXT
-
-
-    def test_crypto_aes_gcm_dec_missing_tag (self):
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        iv = enc.iv
-        dec = crypto.AES_GCM_context (crypto.DECRYPT, key, TEST_AES_GCM_AAD, iv = iv)
-        ok, ct = enc.process_chunk (TEST_PLAINTEXT)
-        ok, _, tag = enc.done ()
-        ok, pt = dec.process_chunk (ct)
-        with pytest.raises (ValueError):
-            ok, _, _ = dec.done ()
+        password       = str (os.urandom (42))
+        encryptor      = crypto.Encrypt (password, TEST_VERSION,
+                                         TEST_PARAMVERSION,
+                                         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
+
+        decryptor      = crypto.Decrypt (password, fixedparts=fixed)
+        decryptor.next (header)
+        plaintext      = decryptor.process (ciphertext)
+        ok, rest       = decryptor.done ()
+        plaintext     += rest
+
+        assert ok
+        assert plaintext == TEST_PLAINTEXT
 
 
     def test_crypto_aes_gcm_dec_bad_tag (self):
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        iv = enc.iv
-        dec = crypto.AES_GCM_context (crypto.DECRYPT, key, TEST_AES_GCM_AAD, iv = iv)
-        ok, ct = enc.process_chunk (TEST_PLAINTEXT)
-        ok, _, tag = enc.done ()
-        ok, pt = dec.process_chunk (ct)
-        with pytest.raises (cryptography.exceptions.InvalidTag):
-            tag = tag[1:] + b"X"
-            ok, _, _ = dec.done (tag)
+        password       = str (os.urandom (42))
+        encryptor      = crypto.Encrypt (password, TEST_VERSION,
+                                         TEST_PARAMVERSION,
+                                         nacl=TEST_STATIC_NACL)
+
+        header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
+        ciphertext     = encryptor.process (TEST_PLAINTEXT)
+        ciphertext2, header, fixed = encryptor.done (header_dummy)
+
+        mut_header     = bytearray (header)
+        mut_header_vw  = memoryview (mut_header)
+        # replace one byte in the tag part of the header
+        second_byte    = mut_header_vw [crypto.HDR_OFF_TAG + 2]
+        mut_header_vw [crypto.HDR_OFF_TAG + 2] = (second_byte + 0x2a) % 256
+        header         = bytes (mut_header)
+
+        decryptor      = crypto.Decrypt (password, fixedparts=fixed)
+        decryptor.next (header)
+        plaintext      = decryptor.process (ciphertext)
+        ok, err        = decryptor.done ()
+
+        assert ok is False
+        assert err == "InvalidTag()"
 
 
     def test_crypto_aes_gcm_enc_multicnk (self):
         cnksiz = 1 << 10
-        orig_pt = fill_mod (1 << 14)
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        iv = enc.iv
-        dec = crypto.AES_GCM_context (crypto.DECRYPT, key, TEST_AES_GCM_AAD, iv = iv)
+        pt    = fill_mod (1 << 14)
+        password       = str (os.urandom (42))
+        encryptor      = crypto.Encrypt (password, TEST_VERSION,
+                                         TEST_PARAMVERSION,
+                                         nacl=TEST_STATIC_NACL)
+        header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
 
         off = 0
         ct = b""
-        while off < len (orig_pt):
-            upto = min (off + cnksiz, len (orig_pt))
-            ok, cnk = enc.process_chunk (orig_pt [off:upto])
-            assert ok
+        while off < len (pt):
+            upto = min (off + cnksiz, len (pt))
+            cnk = encryptor.process (pt [off:upto])
             ct += cnk
             off += cnksiz
-        ok, _, tag = enc.done ()
-        assert ok
-        assert tag
-        assert len (ct) == len (orig_pt)
+        cnk, header, fixed = encryptor.done (header_dummy)
+        ct += cnk
+
+        assert len (pt) == len (ct)
 
 
     def test_crypto_aes_gcm_dec_multicnk (self):
-        cnksiz = 1 << 10
-        orig_pt = fill_mod (1 << 14)
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        iv = enc.iv
-        dec = crypto.AES_GCM_context (crypto.DECRYPT, key, TEST_AES_GCM_AAD, iv = iv)
+        cnksiz         = 1 << 10
+        orig_pt        = fill_mod (1 << 14)
+        password       = str (os.urandom (42))
+        encryptor      = crypto.Encrypt (password, TEST_VERSION,
+                                         TEST_PARAMVERSION,
+                                         nacl=TEST_STATIC_NACL)
+        header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
 
         off = 0
         ct = b""
         while off < len (orig_pt):
             upto = min (off + cnksiz, len (orig_pt))
-            ok, cnk = enc.process_chunk (orig_pt [off:upto])
+            cnk = encryptor.process (orig_pt [off:upto])
             ct += cnk
             off += cnksiz
-        ok, _, tag = enc.done ()
+        cnk, header, fixed = encryptor.done (header_dummy)
+        ct += cnk
 
+        decryptor      = crypto.Decrypt (password, fixedparts=fixed)
+        decryptor.next (header)
         off = 0
-        pt = b""
+        pt  = b""
         while off < len (orig_pt):
             upto = min (off + cnksiz, len (orig_pt))
-            ok, cnk = dec.process_chunk (ct [off:upto])
+            cnk = decryptor.process (ct [off:upto])
             pt += cnk
             off += cnksiz
-        ok, _, _ = dec.done (tag)
 
+        ok, cnk = decryptor.done ()
+        assert ok
+
+        pt += cnk
         assert pt == orig_pt
 
 
     def test_crypto_aes_gcm_dec_multicnk_bad_tag (self):
-        cnksiz = 1 << 10
-        orig_pt = fill_mod (1 << 14)
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        iv = enc.iv
-        dec = crypto.AES_GCM_context (crypto.DECRYPT, key, TEST_AES_GCM_AAD, iv = iv)
+        cnksiz         = 1 << 10
+        orig_pt        = fill_mod (1 << 14)
+        password       = str (os.urandom (42))
+        encryptor      = crypto.Encrypt (password, TEST_VERSION,
+                                         TEST_PARAMVERSION,
+                                         nacl=TEST_STATIC_NACL)
+        header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
 
         off = 0
         ct = b""
         while off < len (orig_pt):
             upto = min (off + cnksiz, len (orig_pt))
-            ok, cnk = enc.process_chunk (orig_pt [off:upto])
+            cnk = encryptor.process (orig_pt [off:upto])
             ct += cnk
             off += cnksiz
-        ok, _, tag = enc.done ()
-
+        cnk, header, fixed = encryptor.done (header_dummy)
+        ct += cnk
+
+        mut_header     = bytearray (header)
+        mut_header_vw  = memoryview (mut_header)
+        # replace one byte in the tag part of the header
+        second_byte    = mut_header_vw [crypto.HDR_OFF_TAG + 2]
+        mut_header_vw [crypto.HDR_OFF_TAG + 2] = (second_byte + 0x2a) % 256
+        header         = bytes (mut_header)
+
+        decryptor      = crypto.Decrypt (password, fixedparts=fixed)
+        decryptor.next (header)
         off = 0
-        pt = b""
+        pt  = b""
         while off < len (orig_pt):
             upto = min (off + cnksiz, len (orig_pt))
-            ok, cnk = dec.process_chunk (ct [off:upto])
+            cnk = decryptor.process (ct [off:upto])
             pt += cnk
             off += cnksiz
 
-        with pytest.raises (cryptography.exceptions.InvalidTag):
-            tag = b"Y" + tag[:-1]
-            ok, _, _ = dec.done (tag)
-
-
-    def test_crypto_aes_gcm_dec_multicnk_missing_tag (self):
-        cnksiz = 1 << 10
-        orig_pt = fill_mod (1 << 14)
-        NaCl = os.urandom (CRYPTO_NACL_SIZE)
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        iv = enc.iv
-        dec = crypto.AES_GCM_context (crypto.DECRYPT, key, TEST_AES_GCM_AAD, iv = iv)
-
-        off = 0
-        ct = b""
-        while off < len (orig_pt):
-            upto = min (off + cnksiz, len (orig_pt))
-            ok, cnk = enc.process_chunk (orig_pt [off:upto])
-            ct += cnk
-            off += cnksiz
-        ok, _, tag = enc.done ()
-
-        off = 0
-        pt = b""
-        while off < len (orig_pt):
-            upto = min (off + cnksiz, len (orig_pt))
-            ok, cnk = dec.process_chunk (ct [off:upto])
-            pt += cnk
-            off += cnksiz
-
-        with pytest.raises (ValueError):
-            ok, _, _ = dec.done ()
-
-
-class ScryptTest (CryptoLayerTest):
-
-    nacl_len  = 16
-    nacl      = binascii.unhexlify(b"0011223344556677"
-                                   b"8899aabbccddeeff")
-
-#   def test_scrypt_keygen (self):
-#       nacl, k = crypto.scrypt_derive (TEST_PASSPHRASE, self.nacl)
-#       assert len (k) == CRYPTO_KEY_SIZE
-#       assert nacl == self.nacl
-
-    ## excessively slow, so disabled
-#   def test_scrypt_keygen_salt_random (self):
-#       _, salted_a = crypto.scrypt_derive (TEST_PASSPHRASE, None)
-#       _, salted_b = crypto.scrypt_derive (TEST_PASSPHRASE, None)
-#       assert salted_a != salted_b
+        ok, err = decryptor.done ()
+        assert ok is False
+        assert err == "InvalidTag()"
 
 
 class HeaderTest (CryptoLayerTest):
@@ -280,7 +240,7 @@ class HeaderTest (CryptoLayerTest):
         meta = faux_hdr()
         ok, hdr = crypto.hdr_make (meta)
         assert ok
-        assert len (hdr) == crypto.I2N_HDR_SIZE
+        assert len (hdr) == crypto.PDTCRYPT_HDR_SIZE
 
 
     def test_crypto_fmt_hdr_make_useless (self):
@@ -292,9 +252,10 @@ class HeaderTest (CryptoLayerTest):
     def test_crypto_fmt_hdr_read (self):
         meta = faux_hdr()
         ok, hdr = crypto.hdr_make (meta)
-        assert ok
-        ok, mmeta = crypto.hdr_read (hdr)
-        assert ok
+        assert ok is True
+        assert hdr is not None
+        mmeta = crypto.hdr_read (hdr)
+        assert mmeta is not None
         for k in meta:
             if meta [k] != mmeta [k]:
                 raise "header mismatch after reading: expected %r, got %r" \
@@ -304,54 +265,31 @@ class HeaderTest (CryptoLayerTest):
     def test_crypto_fmt_hdr_read_trailing_garbage (self):
         meta = faux_hdr()
         ok, hdr = crypto.hdr_make (meta)
+        ok, hdr = crypto.hdr_make (meta)
+        assert ok is True
+        assert hdr is not None
         hdr += b"-junk"
-        ok, msg = crypto.hdr_read (hdr)
-        assert ok is False
-        assert msg.startswith ("error reading header from")
+        with pytest.raises (crypto.InvalidHeader):
+            _ = crypto.hdr_read (hdr)
 
 
     def test_crypto_fmt_hdr_read_leading_garbage (self):
         meta = faux_hdr()
         ok, hdr = crypto.hdr_make (meta)
+        ok, hdr = crypto.hdr_make (meta)
+        assert ok is True
+        assert hdr is not None
         hdr = b"junk-" + hdr
-        ok, msg = crypto.hdr_read (hdr)
-        assert ok is False
-        assert msg.startswith ("error reading header from")
+        with pytest.raises (crypto.InvalidHeader):
+            _ = crypto.hdr_read (hdr)
 
 
     def test_crypto_fmt_hdr_inner_garbage (self):
         meta = faux_hdr()
         ok, hdr = crypto.hdr_make (meta)
-        hdr = hdr[:len(hdr)//2] + b"junk-" + hdr[len(hdr)//2:]
-        ok, msg = crypto.hdr_read (hdr)
-        assert ok is False
-        assert msg.startswith ("error reading header from")
-
-class TagTest (CryptoLayerTest):
-
-    def test_crypto_tag_fmt (self):
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        ok, _ = enc.process_chunk (TEST_PLAINTEXT)
-        assert ok
-        ok, _, tag = enc.done ()
-        assert ok
-        assert tag
-        tagged = crypto.tag_fmt (tag)
-        assert len (tagged) == CRYPTO_TAG_SIZE
-
-
-    def test_crypto_tag_read (self):
-        key  = os.urandom (CRYPTO_KEY_SIZE)
-        enc = crypto.AES_GCM_context (crypto.ENCRYPT, key, TEST_AES_GCM_AAD)
-        ok, _ = enc.process_chunk (TEST_PLAINTEXT)
-        assert ok
-        ok, _, tag = enc.done ()
-        assert ok
-        assert tag
-        tagged = crypto.tag_fmt (tag)
-        (ok, ttag) = crypto.tag_read (tagged)
         assert ok
-        assert tag == ttag
+        data = hdr[:len(hdr)//2] + b"junk-" + hdr[len(hdr)//2:]
+        with pytest.raises (crypto.InvalidHeader):
+            _ = crypto.hdr_read (data)