diff --git a/internal/stupidgcm/common_test.go b/internal/stupidgcm/common_test.go new file mode 100644 index 0000000..28f3308 --- /dev/null +++ b/internal/stupidgcm/common_test.go @@ -0,0 +1,152 @@ +package stupidgcm + +import ( + "bytes" + "crypto/cipher" + "encoding/hex" + "testing" +) + +// testEncryptDecrypt encrypts and decrypts using both stupidgcm and Go's built-in +// GCM implementation and verifies that the results are identical. +func testEncryptDecrypt(t *testing.T, c1 cipher.AEAD, c2 cipher.AEAD) { + if c1.NonceSize() != c2.NonceSize() { + t.Fatal("different NonceSize") + } + if c1.Overhead() != c2.Overhead() { + t.Fatal("different Overhead") + } + + authData := randBytes(24) + iv := randBytes(c1.NonceSize()) + + dst := make([]byte, 71) // 71 = arbitrary length + + // Check all block sizes from 1 to 5000 + for i := 1; i < 5000; i++ { + in := make([]byte, i) + + c1out := c1.Seal(dst, iv, in, authData) + c2out := c2.Seal(dst, iv, in, authData) + + // Ciphertext must be identical to Go GCM + if !bytes.Equal(c1out, c2out) { + t.Fatalf("Compare failed for encryption, size %d", i) + t.Log("c1out:") + t.Log("\n" + hex.Dump(c1out)) + t.Log("c2out:") + t.Log("\n" + hex.Dump(c2out)) + } + + c1out2, sErr := c1.Open(dst, iv, c1out[len(dst):], authData) + if sErr != nil { + t.Fatal(sErr) + } + c2out2, gErr := c2.Open(dst, iv, c2out[len(dst):], authData) + if gErr != nil { + t.Fatal(gErr) + } + + // Plaintext must be identical to Go GCM + if !bytes.Equal(c1out2, c2out2) { + t.Fatalf("Compare failed for decryption, size %d", i) + } + } +} + +// Seal re-uses the "dst" buffer it is large enough. +// Check that this works correctly by testing different "dst" capacities from +// 5000 to 16 and "in" lengths from 1 to 5000. +func testInplaceSeal(t *testing.T, c1 cipher.AEAD, c2 cipher.AEAD) { + authData := randBytes(24) + iv := randBytes(c1.NonceSize()) + + max := 5016 + // Check all block sizes from 1 to 5000 + for i := 1; i < max-16; i++ { + in := make([]byte, i) + dst := make([]byte, max-i) + dst = dst[:16] + + c1out := c1.Seal(dst, iv, in, authData) + dst2 := make([]byte, 16) + c2out := c2.Seal(dst2, iv, in, authData) + + // Ciphertext must be identical to Go GCM + if !bytes.Equal(c1out, c2out) { + t.Fatalf("Compare failed for encryption, size %d", i) + t.Log("sOut:") + t.Log("\n" + hex.Dump(c1out)) + t.Log("gOut:") + t.Log("\n" + hex.Dump(c2out)) + } + } +} + +// testInplaceOpen - Open re-uses the "dst" buffer it is large enough. +// Check that this works correctly by testing different "dst" capacities from +// 5000 to 16 and "in" lengths from 1 to 5000. +func testInplaceOpen(t *testing.T, c1 cipher.AEAD, c2 cipher.AEAD) { + authData := randBytes(24) + iv := randBytes(c1.NonceSize()) + + max := 5016 + // Check all block sizes from 1 to 5000 + for i := 1; i < max-c1.NonceSize(); i++ { + in := make([]byte, i) + + c2ciphertext := c2.Seal(iv, iv, in, authData) + + dst := make([]byte, max-i) + // sPlaintext ... stupidgcm plaintext + c1plaintext, err := c1.Open(dst[:0], iv, c2ciphertext[c1.NonceSize():], authData) + if err != nil { + t.Fatal(err) + } + + // Plaintext must be identical to Go GCM + if !bytes.Equal(in, c1plaintext) { + t.Fatalf("Compare failed, i=%d", i) + } + } +} + +// testCorruption verifies that changes in the ciphertext result in a decryption +// error +func testCorruption(t *testing.T, c cipher.AEAD) { + authData := randBytes(24) + iv := randBytes(c.NonceSize()) + + in := make([]byte, 354) + out := c.Seal(nil, iv, in, authData) + out2, sErr := c.Open(nil, iv, out, authData) + if sErr != nil { + t.Fatal(sErr) + } + if !bytes.Equal(in, out2) { + t.Fatalf("Compare failed") + } + + // Corrupt first byte + out[0]++ + out2, sErr = c.Open(nil, iv, out, authData) + if sErr == nil || out2 != nil { + t.Fatalf("Should have gotten error") + } + out[0]-- + + // Corrupt last byte + out[len(out)-1]++ + out2, sErr = c.Open(nil, iv, out, authData) + if sErr == nil || out2 != nil { + t.Fatalf("Should have gotten error") + } + out[len(out)-1]-- + + // Append one byte + out = append(out, 0) + out2, sErr = c.Open(nil, iv, out, authData) + if sErr == nil || out2 != nil { + t.Fatalf("Should have gotten error") + } +} diff --git a/internal/stupidgcm/stupidchacha_test.go b/internal/stupidgcm/stupidchacha_test.go index 010055f..c1086d1 100644 --- a/internal/stupidgcm/stupidchacha_test.go +++ b/internal/stupidgcm/stupidchacha_test.go @@ -6,8 +6,6 @@ package stupidgcm import ( - "bytes" - "encoding/hex" "testing" "golang.org/x/crypto/chacha20poly1305" @@ -17,46 +15,13 @@ import ( // GCM implementation and verifies that the results are identical. func TestEncryptDecryptChacha(t *testing.T) { key := randBytes(32) - sGCM := newChacha20poly1305(key) - authData := randBytes(24) - iv := randBytes(sGCM.NonceSize()) - dst := make([]byte, 71) // 71 = random length - - gGCM, err := chacha20poly1305.New(key) + c := newChacha20poly1305(key) + ref, err := chacha20poly1305.New(key) if err != nil { t.Fatal(err) } - // Check all block sizes from 1 to 5000 - for i := 1; i < 5000; i++ { - in := make([]byte, i) - - sOut := sGCM.Seal(dst, iv, in, authData) - gOut := gGCM.Seal(dst, iv, in, authData) - - // Ciphertext must be identical to Go GCM - if !bytes.Equal(sOut, gOut) { - t.Fatalf("Compare failed for encryption, size %d", i) - t.Log("sOut:") - t.Log("\n" + hex.Dump(sOut)) - t.Log("gOut:") - t.Log("\n" + hex.Dump(gOut)) - } - - sOut2, sErr := sGCM.Open(dst, iv, sOut[len(dst):], authData) - if sErr != nil { - t.Fatal(sErr) - } - gOut2, gErr := gGCM.Open(dst, iv, gOut[len(dst):], authData) - if gErr != nil { - t.Fatal(gErr) - } - - // Plaintext must be identical to Go GCM - if !bytes.Equal(sOut2, gOut2) { - t.Fatalf("Compare failed for decryption, size %d", i) - } - } + testEncryptDecrypt(t, c, ref) } // Seal re-uses the "dst" buffer it is large enough. @@ -64,34 +29,13 @@ func TestEncryptDecryptChacha(t *testing.T) { // 5000 to 16 and "in" lengths from 1 to 5000. func TestInplaceSealChacha(t *testing.T) { key := randBytes(32) - sGCM := newChacha20poly1305(key) - authData := randBytes(24) - iv := randBytes(sGCM.NonceSize()) - - gGCM, err := chacha20poly1305.New(key) + c := newChacha20poly1305(key) + ref, err := chacha20poly1305.New(key) if err != nil { t.Fatal(err) } - max := 5016 - // Check all block sizes from 1 to 5000 - for i := 1; i < max-len(iv); i++ { - in := make([]byte, i) - dst := make([]byte, max-i) - dst = dst[:len(iv)] - sOut := sGCM.Seal(dst, iv, in, authData) - dst2 := make([]byte, len(iv)) - gOut := gGCM.Seal(dst2, iv, in, authData) - - // Ciphertext must be identical to Go GCM - if !bytes.Equal(sOut, gOut) { - t.Fatalf("Compare failed for encryption, size %d", i) - t.Log("sOut:") - t.Log("\n" + hex.Dump(sOut)) - t.Log("gOut:") - t.Log("\n" + hex.Dump(gOut)) - } - } + testInplaceSeal(t, c, ref) } // Open re-uses the "dst" buffer it is large enough. @@ -99,73 +43,20 @@ func TestInplaceSealChacha(t *testing.T) { // 5000 to 16 and "in" lengths from 1 to 5000. func TestInplaceOpenChacha(t *testing.T) { key := randBytes(32) - sGCM := newChacha20poly1305(key) - authData := randBytes(24) - iv := randBytes(sGCM.NonceSize()) - - gGCM, err := chacha20poly1305.New(key) + c := newChacha20poly1305(key) + ref, err := chacha20poly1305.New(key) if err != nil { t.Fatal(err) } - max := 5016 - // Check all block sizes from 1 to 5000 - for i := 1; i < max-len(iv); i++ { - in := make([]byte, i) - gCiphertext := gGCM.Seal(iv, iv, in, authData) - - dst := make([]byte, max-i) - // sPlaintext ... stupidgcm plaintext - sPlaintext, err := sGCM.Open(dst[:0], iv, gCiphertext[len(iv):], authData) - if err != nil { - t.Fatal(err) - } - - // Plaintext must be identical to Go GCM - if !bytes.Equal(in, sPlaintext) { - t.Fatalf("Compare failed, i=%d", i) - } - } + testInplaceOpen(t, c, ref) } // TestCorruption verifies that changes in the ciphertext result in a decryption // error func TestCorruptionChacha(t *testing.T) { key := randBytes(32) - sGCM := newChacha20poly1305(key) - authData := randBytes(24) - iv := randBytes(sGCM.NonceSize()) + c := newChacha20poly1305(key) - in := make([]byte, 354) - sOut := sGCM.Seal(nil, iv, in, authData) - sOut2, sErr := sGCM.Open(nil, iv, sOut, authData) - if sErr != nil { - t.Fatal(sErr) - } - if !bytes.Equal(in, sOut2) { - t.Fatalf("Compare failed") - } - - // Corrupt first byte - sOut[0]++ - sOut2, sErr = sGCM.Open(nil, iv, sOut, authData) - if sErr == nil || sOut2 != nil { - t.Fatalf("Should have gotten error") - } - sOut[0]-- - - // Corrupt last byte - sOut[len(sOut)-1]++ - sOut2, sErr = sGCM.Open(nil, iv, sOut, authData) - if sErr == nil || sOut2 != nil { - t.Fatalf("Should have gotten error") - } - sOut[len(sOut)-1]-- - - // Append one byte - sOut = append(sOut, 0) - sOut2, sErr = sGCM.Open(nil, iv, sOut, authData) - if sErr == nil || sOut2 != nil { - t.Fatalf("Should have gotten error") - } + testCorruption(t, c) } diff --git a/internal/stupidgcm/stupidgcm_test.go b/internal/stupidgcm/stupidgcm_test.go index 18732df..968034e 100644 --- a/internal/stupidgcm/stupidgcm_test.go +++ b/internal/stupidgcm/stupidgcm_test.go @@ -6,11 +6,9 @@ package stupidgcm import ( - "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" - "encoding/hex" "log" "testing" ) @@ -30,9 +28,6 @@ func randBytes(n int) []byte { func TestEncryptDecrypt(t *testing.T) { key := randBytes(32) sGCM := New(key, false) - authData := randBytes(24) - iv := randBytes(16) - dst := make([]byte, 71) // 71 = random length gAES, err := aes.NewCipher(key) if err != nil { @@ -43,36 +38,7 @@ func TestEncryptDecrypt(t *testing.T) { t.Fatal(err) } - // Check all block sizes from 1 to 5000 - for i := 1; i < 5000; i++ { - in := make([]byte, i) - - sOut := sGCM.Seal(dst, iv, in, authData) - gOut := gGCM.Seal(dst, iv, in, authData) - - // Ciphertext must be identical to Go GCM - if !bytes.Equal(sOut, gOut) { - t.Fatalf("Compare failed for encryption, size %d", i) - t.Log("sOut:") - t.Log("\n" + hex.Dump(sOut)) - t.Log("gOut:") - t.Log("\n" + hex.Dump(gOut)) - } - - sOut2, sErr := sGCM.Open(dst, iv, sOut[len(dst):], authData) - if sErr != nil { - t.Fatal(sErr) - } - gOut2, gErr := gGCM.Open(dst, iv, gOut[len(dst):], authData) - if gErr != nil { - t.Fatal(gErr) - } - - // Plaintext must be identical to Go GCM - if !bytes.Equal(sOut2, gOut2) { - t.Fatalf("Compare failed for decryption, size %d", i) - } - } + testEncryptDecrypt(t, sGCM, gGCM) } // Seal re-uses the "dst" buffer it is large enough. @@ -81,8 +47,6 @@ func TestEncryptDecrypt(t *testing.T) { func TestInplaceSeal(t *testing.T) { key := randBytes(32) sGCM := New(key, false) - authData := randBytes(24) - iv := randBytes(16) gAES, err := aes.NewCipher(key) if err != nil { @@ -92,26 +56,8 @@ func TestInplaceSeal(t *testing.T) { if err != nil { t.Fatal(err) } - max := 5016 - // Check all block sizes from 1 to 5000 - for i := 1; i < max-16; i++ { - in := make([]byte, i) - dst := make([]byte, max-i) - dst = dst[:16] - sOut := sGCM.Seal(dst, iv, in, authData) - dst2 := make([]byte, 16) - gOut := gGCM.Seal(dst2, iv, in, authData) - - // Ciphertext must be identical to Go GCM - if !bytes.Equal(sOut, gOut) { - t.Fatalf("Compare failed for encryption, size %d", i) - t.Log("sOut:") - t.Log("\n" + hex.Dump(sOut)) - t.Log("gOut:") - t.Log("\n" + hex.Dump(gOut)) - } - } + testInplaceSeal(t, sGCM, gGCM) } // Open re-uses the "dst" buffer it is large enough. @@ -120,8 +66,6 @@ func TestInplaceSeal(t *testing.T) { func TestInplaceOpen(t *testing.T) { key := randBytes(32) sGCM := New(key, false) - authData := randBytes(24) - iv := randBytes(16) gAES, err := aes.NewCipher(key) if err != nil { @@ -131,25 +75,8 @@ func TestInplaceOpen(t *testing.T) { if err != nil { t.Fatal(err) } - max := 5016 - // Check all block sizes from 1 to 5000 - for i := 1; i < max-16; i++ { - in := make([]byte, i) - gCiphertext := gGCM.Seal(iv, iv, in, authData) - - dst := make([]byte, max-i) - // sPlaintext ... stupidgcm plaintext - sPlaintext, err := sGCM.Open(dst[:0], iv, gCiphertext[16:], authData) - if err != nil { - t.Fatal(err) - } - - // Plaintext must be identical to Go GCM - if !bytes.Equal(in, sPlaintext) { - t.Fatalf("Compare failed, i=%d", i) - } - } + testInplaceOpen(t, sGCM, gGCM) } // TestCorruption verifies that changes in the ciphertext result in a decryption @@ -157,39 +84,6 @@ func TestInplaceOpen(t *testing.T) { func TestCorruption(t *testing.T) { key := randBytes(32) sGCM := New(key, false) - authData := randBytes(24) - iv := randBytes(16) - in := make([]byte, 354) - sOut := sGCM.Seal(nil, iv, in, authData) - sOut2, sErr := sGCM.Open(nil, iv, sOut, authData) - if sErr != nil { - t.Fatal(sErr) - } - if !bytes.Equal(in, sOut2) { - t.Fatalf("Compare failed") - } - - // Corrupt first byte - sOut[0]++ - sOut2, sErr = sGCM.Open(nil, iv, sOut, authData) - if sErr == nil || sOut2 != nil { - t.Fatalf("Should have gotten error") - } - sOut[0]-- - - // Corrupt last byte - sOut[len(sOut)-1]++ - sOut2, sErr = sGCM.Open(nil, iv, sOut, authData) - if sErr == nil || sOut2 != nil { - t.Fatalf("Should have gotten error") - } - sOut[len(sOut)-1]-- - - // Append one byte - sOut = append(sOut, 0) - sOut2, sErr = sGCM.Open(nil, iv, sOut, authData) - if sErr == nil || sOut2 != nil { - t.Fatalf("Should have gotten error") - } + testCorruption(t, sGCM) }