]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/internal/fips140test: add AES ACVP tests
authorDaniel McCarney <daniel@binaryparadox.net>
Wed, 11 Dec 2024 19:48:00 +0000 (14:48 -0500)
committerGopher Robot <gobot@golang.org>
Fri, 7 Feb 2025 16:58:24 +0000 (08:58 -0800)
Adds ACVP AES test coverage for:

* AES CBC
* AES CTR
* AES GCM (both internal & external iv gen)

For AES key sizes of 128, 192, and 256 bits, based on the NIST spec:

  https://pages.nist.gov/ACVP/draft-celi-acvp-symmetric.html

ECB mode is excluded based on upcoming policy changes forbidding its
use.

Internal IV gen is excluded from the go-acvp static test data since it's
non-deterministic based on the DRBG.

Updates #69642

Change-Id: I34f471725e2f1a2f5d32ab9877bde153abf2db0f
Reviewed-on: https://go-review.googlesource.com/c/go/+/627655
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
src/crypto/internal/fips140test/acvp_capabilities.json
src/crypto/internal/fips140test/acvp_test.config.json
src/crypto/internal/fips140test/acvp_test.go

index ff94d3b6ba01ad9274cc91ea768bb794d75fb1f6..68d8e3bd2a6a254ad629c4d94994c90f889abc65 100644 (file)
   {"algorithm":"ECDSA","mode":"keyVer","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"]},
   {"algorithm":"ECDSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]},
   {"algorithm":"ECDSA","mode":"sigVer","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]},
-  {"algorithm":"DetECDSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]}
+  {"algorithm":"DetECDSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]},
+
+  {"algorithm":"ACVP-AES-CBC","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"revision":"1.0"},
+  {"algorithm":"ACVP-AES-CTR","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":8,"max":128,"increment":8}],"incrementalCounter":true,"overflowCounter":true,"performCounterTests":true,"revision":"1.0"},
+  {"algorithm":"ACVP-AES-GCM","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":0,"max":65536,"increment":8}],"aadLen":[{"min":0,"max":65536,"increment":8}],"tagLen":[96,104,112,120,128],"ivLen":[96],"ivGen":"external","revision":"1.0"},
+  {"algorithm":"ACVP-AES-GCM","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":0,"max":65536,"increment":8}],"aadLen":[{"min":0,"max":65536,"increment":8}],"tagLen":[128],"ivLen":[96],"ivGen":"internal","ivGenMode":"8.2.2","revision":"1.0"}
 ]
index 4c1879380c2c911f4aab2e27ffc419270efb8a3e..d994f5b7c552b076ff18cb7ac4ecf489b6ded3a9 100644 (file)
@@ -31,5 +31,9 @@
 
   {"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"},
 
-  {"Wrapper": "go", "In": "vectors/ECDSA.bz2", "Out": "expected/ECDSA.bz2"}
+  {"Wrapper": "go", "In": "vectors/ECDSA.bz2", "Out": "expected/ECDSA.bz2"},
+
+  {"Wrapper": "go", "In": "vectors/ACVP-AES-CBC.bz2", "Out": "expected/ACVP-AES-CBC.bz2"},
+  {"Wrapper": "go", "In": "vectors/ACVP-AES-CTR.bz2", "Out": "expected/ACVP-AES-CTR.bz2"},
+  {"Wrapper": "go", "In": "vectors/ACVP-AES-GCM.bz2", "Out": "expected/ACVP-AES-GCM.bz2"}
 ]
\ No newline at end of file
index 1ee13c3f1d9b90b872d66538076b4d05225fb7a6..2637ccc3e40add908df8a0277449b354624c4130 100644 (file)
@@ -24,6 +24,8 @@ import (
        "crypto/elliptic"
        "crypto/internal/cryptotest"
        "crypto/internal/fips140"
+       "crypto/internal/fips140/aes"
+       "crypto/internal/fips140/aes/gcm"
        "crypto/internal/fips140/ecdsa"
        "crypto/internal/fips140/ed25519"
        "crypto/internal/fips140/edwards25519"
@@ -82,6 +84,13 @@ const (
        ecdsaSigTypeDeterministic
 )
 
+type aesDirection int
+
+const (
+       aesEncrypt aesDirection = iota
+       aesDecrypt
+)
+
 var (
        // SHA2 algorithm capabilities:
        //   https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#section-7.2
@@ -97,6 +106,8 @@ var (
        //   https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-7
        // ECDSA and DetECDSA algorithm capabilities:
        //   https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#section-7
+       // AES algorithm capabilities:
+       //   https://pages.nist.gov/ACVP/draft-celi-acvp-symmetric.html#section-7.3
        //go:embed acvp_capabilities.json
        capabilitiesJson []byte
 
@@ -169,6 +180,15 @@ var (
                "ECDSA/sigGen":    cmdEcdsaSigGenAft(ecdsaSigTypeNormal),
                "ECDSA/sigVer":    cmdEcdsaSigVerAft(),
                "DetECDSA/sigGen": cmdEcdsaSigGenAft(ecdsaSigTypeDeterministic),
+
+               "AES-CBC/encrypt":        cmdAesCbc(aesEncrypt),
+               "AES-CBC/decrypt":        cmdAesCbc(aesDecrypt),
+               "AES-CTR/encrypt":        cmdAesCtr(aesEncrypt),
+               "AES-CTR/decrypt":        cmdAesCtr(aesDecrypt),
+               "AES-GCM/seal":           cmdAesGcmSeal(false),
+               "AES-GCM/open":           cmdAesGcmOpen(false),
+               "AES-GCM-randnonce/seal": cmdAesGcmSeal(true),
+               "AES-GCM-randnonce/open": cmdAesGcmOpen(true),
        }
 )
 
@@ -961,6 +981,179 @@ func lookupCurve(name string) (elliptic.Curve, error) {
        return c, nil
 }
 
+func cmdAesCbc(direction aesDirection) command {
+       return command{
+               requiredArgs: 4, // Key, ciphertext or plaintext, IV, num iterations
+               handler: func(args [][]byte) ([][]byte, error) {
+                       if direction != aesEncrypt && direction != aesDecrypt {
+                               panic("invalid AES direction")
+                       }
+
+                       key := args[0]
+                       input := args[1]
+                       iv := args[2]
+                       numIterations := binary.LittleEndian.Uint32(args[3])
+
+                       blockCipher, err := aes.New(key)
+                       if err != nil {
+                               return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err)
+                       }
+
+                       if len(input)%blockCipher.BlockSize() != 0 || len(input) == 0 {
+                               return nil, fmt.Errorf("invalid ciphertext/plaintext size %d: not a multiple of block size %d",
+                                       len(input), blockCipher.BlockSize())
+                       }
+
+                       if blockCipher.BlockSize() != len(iv) {
+                               return nil, fmt.Errorf("invalid IV size: expected %d, got %d", blockCipher.BlockSize(), len(iv))
+                       }
+
+                       result := make([]byte, len(input))
+                       prevResult := make([]byte, len(input))
+                       prevInput := make([]byte, len(input))
+
+                       for i := uint32(0); i < numIterations; i++ {
+                               copy(prevResult, result)
+
+                               if i > 0 {
+                                       if direction == aesEncrypt {
+                                               copy(iv, result)
+                                       } else {
+                                               copy(iv, prevInput)
+                                       }
+                               }
+
+                               if direction == aesEncrypt {
+                                       cbcEnc := aes.NewCBCEncrypter(blockCipher, [16]byte(iv))
+                                       cbcEnc.CryptBlocks(result, input)
+                               } else {
+                                       cbcDec := aes.NewCBCDecrypter(blockCipher, [16]byte(iv))
+                                       cbcDec.CryptBlocks(result, input)
+                               }
+
+                               if direction == aesDecrypt {
+                                       copy(prevInput, input)
+                               }
+
+                               if i == 0 {
+                                       copy(input, iv)
+                               } else {
+                                       copy(input, prevResult)
+                               }
+                       }
+
+                       return [][]byte{result, prevResult}, nil
+               },
+       }
+}
+
+func cmdAesCtr(direction aesDirection) command {
+       return command{
+               requiredArgs: 4, // Key, ciphertext or plaintext, initial counter, num iterations (constant 1)
+               handler: func(args [][]byte) ([][]byte, error) {
+                       if direction != aesEncrypt && direction != aesDecrypt {
+                               panic("invalid AES direction")
+                       }
+
+                       key := args[0]
+                       input := args[1]
+                       iv := args[2]
+                       numIterations := binary.LittleEndian.Uint32(args[3])
+                       if numIterations != 1 {
+                               return nil, fmt.Errorf("invalid num iterations: expected 1, got %d", numIterations)
+                       }
+
+                       if len(iv) != aes.BlockSize {
+                               return nil, fmt.Errorf("invalid IV size: expected %d, got %d", aes.BlockSize, len(iv))
+                       }
+
+                       blockCipher, err := aes.New(key)
+                       if err != nil {
+                               return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err)
+                       }
+
+                       result := make([]byte, len(input))
+                       stream := aes.NewCTR(blockCipher, iv)
+                       stream.XORKeyStream(result, input)
+
+                       return [][]byte{result}, nil
+               },
+       }
+}
+
+func cmdAesGcmSeal(randNonce bool) command {
+       return command{
+               requiredArgs: 5, // tag len, key, plaintext, nonce (empty for randNonce), additional data
+               handler: func(args [][]byte) ([][]byte, error) {
+                       tagLen := binary.LittleEndian.Uint32(args[0])
+                       key := args[1]
+                       plaintext := args[2]
+                       nonce := args[3]
+                       additionalData := args[4]
+
+                       blockCipher, err := aes.New(key)
+                       if err != nil {
+                               return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err)
+                       }
+
+                       aesGCM, err := gcm.New(blockCipher, 12, int(tagLen))
+                       if err != nil {
+                               return nil, fmt.Errorf("creating AES-GCM with tag len %d: %w", tagLen, err)
+                       }
+
+                       var ct []byte
+                       if !randNonce {
+                               ct = aesGCM.Seal(nil, nonce, plaintext, additionalData)
+                       } else {
+                               var internalNonce [12]byte
+                               ct = make([]byte, len(plaintext)+16)
+                               gcm.SealWithRandomNonce(aesGCM, internalNonce[:], ct, plaintext, additionalData)
+                               // acvptool expects the internally generated nonce to be appended to the end of the ciphertext.
+                               ct = append(ct, internalNonce[:]...)
+                       }
+
+                       return [][]byte{ct}, nil
+               },
+       }
+}
+
+func cmdAesGcmOpen(randNonce bool) command {
+       return command{
+               requiredArgs: 5, // tag len, key, ciphertext, nonce (empty for randNonce), additional data
+               handler: func(args [][]byte) ([][]byte, error) {
+
+                       tagLen := binary.LittleEndian.Uint32(args[0])
+                       key := args[1]
+                       ciphertext := args[2]
+                       nonce := args[3]
+                       additionalData := args[4]
+
+                       blockCipher, err := aes.New(key)
+                       if err != nil {
+                               return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err)
+                       }
+
+                       aesGCM, err := gcm.New(blockCipher, 12, int(tagLen))
+                       if err != nil {
+                               return nil, fmt.Errorf("creating AES-GCM with tag len %d: %w", tagLen, err)
+                       }
+
+                       if randNonce {
+                               // for randNonce tests acvptool appends the nonce to the end of the ciphertext.
+                               nonce = ciphertext[len(ciphertext)-12:]
+                               ciphertext = ciphertext[:len(ciphertext)-12]
+                       }
+
+                       pt, err := aesGCM.Open(nil, nonce, ciphertext, additionalData)
+                       if err != nil {
+                               return [][]byte{{0}, nil}, nil
+                       }
+
+                       return [][]byte{{1}, pt}, nil
+               },
+       }
+}
+
 func TestACVP(t *testing.T) {
        testenv.SkipIfShortAndSlow(t)