// Copyright 2021 The BoringSSL Authors // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // testmodulewrapper is a modulewrapper binary that works with acvptool and // implements the primitives that BoringSSL's modulewrapper doesn't, so that // we have something that can exercise all the code in avcptool. package main import ( "bytes" "crypto" "crypto/aes" "crypto/cipher" "crypto/ed25519" "crypto/hmac" "crypto/rand" "crypto/sha256" "crypto/sha512" "encoding/binary" "errors" "fmt" "hash" "io" "os" "filippo.io/edwards25519" "golang.org/x/crypto/hkdf" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/sha3" "golang.org/x/crypto/xts" ) var ( output io.Writer outputBuffer *bytes.Buffer ) var handlers = map[string]func([][]byte) error{ "flush": flush, "getConfig": getConfig, "KDF-counter": kdfCounter, "AES-XTS/encrypt": xtsEncrypt, "AES-XTS/decrypt": xtsDecrypt, "HKDF/SHA2-256": hkdfMAC, "hmacDRBG-reseed/SHA2-256": hmacDRBGReseed, "hmacDRBG-pr/SHA2-256": hmacDRBGPredictionResistance, "AES-CBC-CS3/encrypt": ctsEncrypt, "AES-CBC-CS3/decrypt": ctsDecrypt, "PBKDF": pbkdf, "EDDSA/keyGen": eddsaKeyGen, "EDDSA/keyVer": eddsaKeyVer, "EDDSA/sigGen": eddsaSigGen, "EDDSA/sigVer": eddsaSigVer, "SHAKE-128": shakeAftVot(sha3.NewShake128), "SHAKE-128/VOT": shakeAftVot(sha3.NewShake128), "SHAKE-128/MCT": shakeMct(sha3.NewShake128), "SHAKE-256": shakeAftVot(sha3.NewShake256), "SHAKE-256/VOT": shakeAftVot(sha3.NewShake256), "SHAKE-256/MCT": shakeMct(sha3.NewShake256), } func flush(args [][]byte) error { if outputBuffer == nil { return nil } if _, err := os.Stdout.Write(outputBuffer.Bytes()); err != nil { return err } outputBuffer = new(bytes.Buffer) output = outputBuffer return nil } func getConfig(args [][]byte) error { if len(args) != 0 { return fmt.Errorf("getConfig received %d args", len(args)) } if err := reply([]byte(`[ { "algorithm": "acvptool", "features": ["batch"] }, { "algorithm": "KDF", "revision": "1.0", "capabilities": [{ "kdfMode": "counter", "macMode": [ "HMAC-SHA2-256" ], "supportedLengths": [{ "min": 8, "max": 4096, "increment": 8 }], "fixedDataOrder": [ "before fixed data" ], "counterLength": [ 32 ] }] }, { "algorithm": "ACVP-AES-XTS", "revision": "1.0", "direction": [ "encrypt", "decrypt" ], "keyLen": [ 128, 256 ], "payloadLen": [ 1024 ], "tweakMode": [ "number" ] }, { "algorithm": "KDA", "mode": "HKDF", "revision": "Sp800-56Cr1", "fixedInfoPattern": "uPartyInfo||vPartyInfo", "encoding": [ "concatenation" ], "hmacAlg": [ "SHA2-256" ], "macSaltMethods": [ "default", "random" ], "l": 256, "z": [256, 384] }, { "algorithm": "hmacDRBG", "revision": "1.0", "predResistanceEnabled": [false, true], "reseedImplemented": true, "capabilities": [{ "mode": "SHA2-256", "derFuncEnabled": false, "entropyInputLen": [ 256 ], "nonceLen": [ 128 ], "persoStringLen": [ 256 ], "additionalInputLen": [ 256 ], "returnedBitsLen": 256 }] }, { "algorithm": "ACVP-AES-CBC-CS3", "revision": "1.0", "payloadLen": [{ "min": 128, "max": 2048, "increment": 8 }], "direction": [ "encrypt", "decrypt" ], "keyLen": [ 128, 256 ] }, { "algorithm": "PBKDF", "revision":"1.0", "capabilities": [{ "iterationCount":[{ "min":1, "max":10000, "increment":1 }], "keyLen": [{ "min":112, "max":4096, "increment":8 }], "passwordLen":[{ "min":8, "max":64, "increment":1 }], "saltLen":[{ "min":128, "max":512, "increment":8 }], "hmacAlg":[ "SHA2-224", "SHA2-256", "SHA2-384", "SHA2-512", "SHA2-512/224", "SHA2-512/256", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512" ] }] }, { "algorithm": "EDDSA", "mode": "keyVer", "revision": "1.0", "curve": ["ED-25519"] }, { "algorithm": "EDDSA", "mode": "sigVer", "revision": "1.0", "pure": true, "preHash": true, "curve": ["ED-25519"] }, { "algorithm": "SHAKE-128", "inBit": false, "outBit": false, "inEmpty": false, "outputLen": [{ "min": 128, "max": 4096, "increment": 8 }], "revision": "1.0" }, { "algorithm": "SHAKE-256", "inBit": false, "outBit": false, "inEmpty": false, "outputLen": [{ "min": 128, "max": 4096, "increment": 8 }], "revision": "1.0" } ]`)); err != nil { return err } return flush(nil) } func kdfCounter(args [][]byte) error { if len(args) != 5 { return fmt.Errorf("KDF received %d args", len(args)) } outputBytes32, prf, counterLocation, key, counterBits32 := args[0], args[1], args[2], args[3], args[4] outputBytes := binary.LittleEndian.Uint32(outputBytes32) counterBits := binary.LittleEndian.Uint32(counterBits32) if !bytes.Equal(prf, []byte("HMAC-SHA2-256")) { return fmt.Errorf("KDF received unsupported PRF %q", string(prf)) } if !bytes.Equal(counterLocation, []byte("before fixed data")) { return fmt.Errorf("KDF received unsupported counter location %q", counterLocation) } if counterBits != 32 { return fmt.Errorf("KDF received unsupported counter length %d", counterBits) } if len(key) == 0 { key = make([]byte, 32) rand.Reader.Read(key) } // See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf section 5.1 if outputBytes+31 < outputBytes { return fmt.Errorf("KDF received excessive output length %d", outputBytes) } n := (outputBytes + 31) / 32 result := make([]byte, 0, 32*n) mac := hmac.New(sha256.New, key) var input [4 + 8]byte var digest []byte rand.Reader.Read(input[4:]) for i := uint32(1); i <= n; i++ { mac.Reset() binary.BigEndian.PutUint32(input[:4], i) mac.Write(input[:]) digest = mac.Sum(digest[:0]) result = append(result, digest...) } return reply(key, input[4:], result[:outputBytes]) } func reply(responses ...[]byte) error { if len(responses) > maxArgs { return fmt.Errorf("%d responses is too many", len(responses)) } var lengths [4 * (1 + maxArgs)]byte binary.LittleEndian.PutUint32(lengths[:4], uint32(len(responses))) for i, response := range responses { binary.LittleEndian.PutUint32(lengths[4*(i+1):4*(i+2)], uint32(len(response))) } lengthsLength := (1 + len(responses)) * 4 if n, err := output.Write(lengths[:lengthsLength]); n != lengthsLength || err != nil { return fmt.Errorf("write failed: %s", err) } for _, response := range responses { if n, err := output.Write(response); n != len(response) || err != nil { return fmt.Errorf("write failed: %s", err) } } return nil } func xtsEncrypt(args [][]byte) error { return doXTS(args, false) } func xtsDecrypt(args [][]byte) error { return doXTS(args, true) } func doXTS(args [][]byte, decrypt bool) error { if len(args) != 3 { return fmt.Errorf("XTS received %d args, wanted 3", len(args)) } key := args[0] msg := args[1] tweak := args[2] if len(msg)%16 != 0 { return fmt.Errorf("XTS received %d-byte msg, need multiple of 16", len(msg)) } if len(tweak) != 16 { return fmt.Errorf("XTS received %d-byte tweak, wanted 16", len(tweak)) } var zeros [8]byte if !bytes.Equal(tweak[8:], zeros[:]) { return errors.New("XTS received tweak with invalid structure. Ensure that configuration specifies a 'number' tweak") } sectorNum := binary.LittleEndian.Uint64(tweak[:8]) c, err := xts.NewCipher(aes.NewCipher, key) if err != nil { return err } if decrypt { c.Decrypt(msg, msg, sectorNum) } else { c.Encrypt(msg, msg, sectorNum) } return reply(msg) } func hkdfMAC(args [][]byte) error { if len(args) != 4 { return fmt.Errorf("HKDF received %d args, wanted 4", len(args)) } key := args[0] salt := args[1] info := args[2] lengthBytes := args[3] if len(lengthBytes) != 4 { return fmt.Errorf("uint32 length was %d bytes long", len(lengthBytes)) } length := binary.LittleEndian.Uint32(lengthBytes) mac := hkdf.New(sha256.New, key, salt, info) ret := make([]byte, length) mac.Read(ret) return reply(ret) } func hmacDRBGReseed(args [][]byte) error { if len(args) != 8 { return fmt.Errorf("hmacDRBG received %d args, wanted 8", len(args)) } outLenBytes, entropy, personalisation, reseedAdditionalData, reseedEntropy, additionalData1, additionalData2, nonce := args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7] if len(outLenBytes) != 4 { return fmt.Errorf("uint32 length was %d bytes long", len(outLenBytes)) } outLen := binary.LittleEndian.Uint32(outLenBytes) out := make([]byte, outLen) drbg := NewHMACDRBG(entropy, nonce, personalisation) drbg.Reseed(reseedEntropy, reseedAdditionalData) drbg.Generate(out, additionalData1) drbg.Generate(out, additionalData2) return reply(out) } func hmacDRBGPredictionResistance(args [][]byte) error { if len(args) != 8 { return fmt.Errorf("hmacDRBG received %d args, wanted 8", len(args)) } outLenBytes, entropy, personalisation, additionalData1, entropy1, additionalData2, entropy2, nonce := args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7] if len(outLenBytes) != 4 { return fmt.Errorf("uint32 length was %d bytes long", len(outLenBytes)) } outLen := binary.LittleEndian.Uint32(outLenBytes) out := make([]byte, outLen) drbg := NewHMACDRBG(entropy, nonce, personalisation) drbg.Reseed(entropy1, additionalData1) drbg.Generate(out, nil) drbg.Reseed(entropy2, additionalData2) drbg.Generate(out, nil) return reply(out) } func swapFinalTwoAESBlocks(d []byte) { var blockNMinus1 [aes.BlockSize]byte copy(blockNMinus1[:], d[len(d)-2*aes.BlockSize:]) copy(d[len(d)-2*aes.BlockSize:], d[len(d)-aes.BlockSize:]) copy(d[len(d)-aes.BlockSize:], blockNMinus1[:]) } func roundUp(n, m int) int { return n + (m-(n%m))%m } func doCTSEncrypt(key, origPlaintext, iv []byte) []byte { // https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38a-add.pdf if len(origPlaintext) < aes.BlockSize { panic("input too small") } plaintext := make([]byte, roundUp(len(origPlaintext), aes.BlockSize)) copy(plaintext, origPlaintext) block, err := aes.NewCipher(key) if err != nil { panic(err) } cbcEncryptor := cipher.NewCBCEncrypter(block, iv) cbcEncryptor.CryptBlocks(plaintext, plaintext) ciphertext := plaintext if len(origPlaintext) > aes.BlockSize { swapFinalTwoAESBlocks(ciphertext) if len(origPlaintext)%16 != 0 { // Truncate the ciphertext ciphertext = ciphertext[:len(ciphertext)-aes.BlockSize+(len(origPlaintext)%aes.BlockSize)] } } if len(ciphertext) != len(origPlaintext) { panic("internal error") } return ciphertext } func doCTSDecrypt(key, origCiphertext, iv []byte) []byte { if len(origCiphertext) < aes.BlockSize { panic("input too small") } ciphertext := make([]byte, roundUp(len(origCiphertext), aes.BlockSize)) copy(ciphertext, origCiphertext) if len(ciphertext) > aes.BlockSize { swapFinalTwoAESBlocks(ciphertext) } block, err := aes.NewCipher(key) if err != nil { panic(err) } cbcDecrypter := cipher.NewCBCDecrypter(block, iv) var plaintext []byte if overhang := len(origCiphertext) % aes.BlockSize; overhang == 0 { cbcDecrypter.CryptBlocks(ciphertext, ciphertext) plaintext = ciphertext } else { ciphertext, finalBlock := ciphertext[:len(ciphertext)-aes.BlockSize], ciphertext[len(ciphertext)-aes.BlockSize:] var plaintextFinalBlock [aes.BlockSize]byte block.Decrypt(plaintextFinalBlock[:], finalBlock) copy(ciphertext[len(ciphertext)-aes.BlockSize+overhang:], plaintextFinalBlock[overhang:]) plaintext = make([]byte, len(origCiphertext)) cbcDecrypter.CryptBlocks(plaintext, ciphertext) for i := 0; i < overhang; i++ { plaintextFinalBlock[i] ^= ciphertext[len(ciphertext)-aes.BlockSize+i] } copy(plaintext[len(ciphertext):], plaintextFinalBlock[:overhang]) } return plaintext } func ctsEncrypt(args [][]byte) error { if len(args) != 4 { return fmt.Errorf("ctsEncrypt received %d args, wanted 4", len(args)) } key, plaintext, iv, numIterations32 := args[0], args[1], args[2], args[3] if len(numIterations32) != 4 || binary.LittleEndian.Uint32(numIterations32) != 1 { return errors.New("only a single iteration supported for ctsEncrypt") } if len(plaintext) < aes.BlockSize { return fmt.Errorf("ctsEncrypt plaintext too short: %d bytes", len(plaintext)) } return reply(doCTSEncrypt(key, plaintext, iv)) } func ctsDecrypt(args [][]byte) error { if len(args) != 4 { return fmt.Errorf("ctsDecrypt received %d args, wanted 4", len(args)) } key, ciphertext, iv, numIterations32 := args[0], args[1], args[2], args[3] if len(numIterations32) != 4 || binary.LittleEndian.Uint32(numIterations32) != 1 { return errors.New("only a single iteration supported for ctsDecrypt") } if len(ciphertext) < aes.BlockSize { return errors.New("ctsDecrypt ciphertext too short") } return reply(doCTSDecrypt(key, ciphertext, iv)) } func pbkdf(args [][]byte) error { if len(args) != 5 { return fmt.Errorf("pbkdf received %d args, wanted 5", len(args)) } hmacName := args[0] var h func() hash.Hash switch string(hmacName) { case "SHA2-224": h = sha256.New224 case "SHA2-256": h = sha256.New case "SHA2-384": h = sha512.New384 case "SHA2-512": h = sha512.New case "SHA2-512/224": h = sha512.New512_224 case "SHA2-512/256": h = sha512.New512_256 case "SHA3-224": h = sha3.New224 case "SHA3-256": h = sha3.New256 case "SHA3-384": h = sha3.New384 case "SHA3-512": h = sha3.New512 default: return fmt.Errorf("pbkdf unknown HMAC algorithm: %q", hmacName) } keyLen := binary.LittleEndian.Uint32(args[1]) / 8 salt, password := args[2], args[3] iterationCount := binary.LittleEndian.Uint32(args[4]) derivedKey := pbkdf2.Key(password, salt, int(iterationCount), int(keyLen), h) return reply(derivedKey) } func eddsaKeyGen(args [][]byte) error { if string(args[0]) != "ED-25519" { return fmt.Errorf("unsupported EDDSA curve: %q", args[0]) } pk, sk, err := ed25519.GenerateKey(nil) if err != nil { return fmt.Errorf("generating EDDSA keypair: %w", err) } // EDDSA/keyGen/AFT responses are d & q, described[0] as: // d The encoded private key point // q The encoded public key point // // Contrary to the description of a "point", d is the private key // seed bytes per FIPS.186-5[1] A.2.3. // // [0]: https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-9.1 // [1]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf return reply(sk.Seed(), pk) } func eddsaKeyVer(args [][]byte) error { if string(args[0]) != "ED-25519" { return fmt.Errorf("unsupported EDDSA curve: %q", args[0]) } if len(args[1]) != ed25519.PublicKeySize { return reply([]byte{0}) } // Verify the point is on the curve. The higher-level ed25519 API does // this at signature verification time so we have to use the lower-level // edwards25519 package to do it here in absence of a signature to verify. if _, err := new(edwards25519.Point).SetBytes(args[1]); err != nil { return reply([]byte{0}) } return reply([]byte{1}) } func eddsaSigGen(args [][]byte) error { if string(args[0]) != "ED-25519" { return fmt.Errorf("unsupported EDDSA curve: %q", args[0]) } sk := ed25519.NewKeyFromSeed(args[1]) msg := args[2] prehash := args[3] context := string(args[4]) var opts ed25519.Options if prehash[0] == 1 { opts.Hash = crypto.SHA512 h := sha512.New() h.Write(msg) msg = h.Sum(nil) // With ed25519 the context is only specified for sigGen tests when using prehashing. // See https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-8.6 opts.Context = context } sig, err := sk.Sign(nil, msg, &opts) if err != nil { return fmt.Errorf("error signing message: %w", err) } return reply(sig) } func eddsaSigVer(args [][]byte) error { if string(args[0]) != "ED-25519" { return fmt.Errorf("unsupported EDDSA curve: %q", args[0]) } msg := args[1] pk := ed25519.PublicKey(args[2]) sig := args[3] prehash := args[4] var opts ed25519.Options if prehash[0] == 1 { opts.Hash = crypto.SHA512 h := sha512.New() h.Write(msg) msg = h.Sum(nil) // Context is only specified for sigGen, not sigVer. // See https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-8.6 } if err := ed25519.VerifyWithOptions(pk, msg, sig, &opts); err != nil { return reply([]byte{0}) } return reply([]byte{1}) } func shakeAftVot(digestFn func() sha3.ShakeHash) func([][]byte) error { return func(args [][]byte) error { if len(args) != 2 { return fmt.Errorf("shakeAftVot received %d args, wanted 2", len(args)) } msg := args[0] outLenBytes := binary.LittleEndian.Uint32(args[1]) h := digestFn() h.Write(msg) digest := make([]byte, outLenBytes) h.Read(digest) return reply(digest) } } func shakeMct(digestFn func() sha3.ShakeHash) func([][]byte) error { return func(args [][]byte) error { if len(args) != 4 { return fmt.Errorf("shakeMct received %d args, wanted 4", len(args)) } md := args[0] minOutBytes := binary.LittleEndian.Uint32(args[1]) maxOutBytes := binary.LittleEndian.Uint32(args[2]) outputLenBytes := binary.LittleEndian.Uint32(args[3]) if outputLenBytes < 2 { return fmt.Errorf("invalid output length: %d", outputLenBytes) } if maxOutBytes < minOutBytes { return fmt.Errorf("invalid maxOutBytes and minOutBytes: %d, %d", maxOutBytes, minOutBytes) } rangeBytes := maxOutBytes - minOutBytes + 1 for i := 0; i < 1000; i++ { // "The MSG[i] input to SHAKE MUST always contain at least 128 bits. If this is not the case // as the previous digest was too short, append empty bits to the rightmost side of the digest." boundary := min(len(md), 16) msg := make([]byte, 16) copy(msg, md[:boundary]) // MD[i] = SHAKE(MSG[i], OutputLen * 8) h := digestFn() h.Write(msg) digest := make([]byte, outputLenBytes) h.Read(digest) md = digest // RightmostOutputBits = 16 rightmost bits of MD[i] as an integer // OutputLen = minOutBytes + (RightmostOutputBits % Range) rightmostOutput := uint32(md[outputLenBytes-2])<<8 | uint32(md[outputLenBytes-1]) outputLenBytes = minOutBytes + (rightmostOutput % rangeBytes) } encodedOutputLenBytes := make([]byte, 4) binary.LittleEndian.PutUint32(encodedOutputLenBytes, outputLenBytes) return reply(md, encodedOutputLenBytes) } } const ( maxArgs = 9 maxArgLength = 1 << 20 maxNameLength = 30 ) func main() { if err := do(); err != nil { fmt.Fprintf(os.Stderr, "%s.\n", err) os.Exit(1) } } func do() error { // In order to exercise pipelining, all output is buffered until a "flush". outputBuffer = new(bytes.Buffer) output = outputBuffer var nums [4 * (1 + maxArgs)]byte var argLengths [maxArgs]uint32 var args [maxArgs][]byte var argsData []byte for { if _, err := io.ReadFull(os.Stdin, nums[:8]); err != nil { return err } numArgs := binary.LittleEndian.Uint32(nums[:4]) if numArgs == 0 { return errors.New("Invalid, zero-argument operation requested") } else if numArgs > maxArgs { return fmt.Errorf("Operation requested with %d args, but %d is the limit", numArgs, maxArgs) } if numArgs > 1 { if _, err := io.ReadFull(os.Stdin, nums[8:4+4*numArgs]); err != nil { return err } } input := nums[4:] var need uint64 for i := uint32(0); i < numArgs; i++ { argLength := binary.LittleEndian.Uint32(input[:4]) if i == 0 && argLength > maxNameLength { return fmt.Errorf("Operation with name of length %d exceeded limit of %d", argLength, maxNameLength) } else if argLength > maxArgLength { return fmt.Errorf("Operation with argument of length %d exceeded limit of %d", argLength, maxArgLength) } need += uint64(argLength) argLengths[i] = argLength input = input[4:] } if need > uint64(cap(argsData)) { argsData = make([]byte, need) } else { argsData = argsData[:need] } if _, err := io.ReadFull(os.Stdin, argsData); err != nil { return err } input = argsData for i := uint32(0); i < numArgs; i++ { args[i] = input[:argLengths[i]] input = input[argLengths[i]:] } name := string(args[0]) if handler, ok := handlers[name]; !ok { return fmt.Errorf("unknown operation %q", name) } else { if err := handler(args[1:numArgs]); err != nil { return err } } } }