acvp: add XTS support. Since we don't have XTS in the FIPS module, this change uses testmodulewrapper for testing. Change-Id: I82117472ea4288d017983fe9cc11d4ba808a972a Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/45064 Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md index 4a93e87..a477492 100644 --- a/util/fipstools/acvp/ACVP.md +++ b/util/fipstools/acvp/ACVP.md
@@ -59,6 +59,8 @@ | AES-KW/seal | (dummy), key, plaintext, (dummy), (dummy) | Ciphertext | | AES-KWP/open | (dummy), key, ciphertext, (dummy), (dummy) | One-byte success flag, plaintext or empty | | AES-KWP/seal | (dummy), key, plaintext, (dummy), (dummy) | Ciphertext | +| AES-XTS/decrypt | Key, ciphertext, tweak | Plaintext | +| AES-XTS/encrypt | Key, plaintext, tweak | Ciphertext | | AES/decrypt | Key, input block, num iterations¹ | Result, Previous result | | AES/encrypt | Key, input block, num iterations¹ | Result, Previous result | | CMAC-AES | Number output bytes, key, message | MAC |
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index c0d48b7..785e7b2 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -80,6 +80,7 @@ "ACVP-AES-ECB": &blockCipher{"AES", 16, 2, true, false, iterateAES}, "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC}, "ACVP-AES-CTR": &blockCipher{"AES-CTR", 16, 1, false, true, nil}, + "ACVP-AES-XTS": &xts{}, "ACVP-TDES-ECB": &blockCipher{"3DES-ECB", 8, 3, true, false, iterate3DES}, "ACVP-TDES-CBC": &blockCipher{"3DES-CBC", 8, 3, true, true, iterate3DESCBC}, "ACVP-AES-GCM": &aead{"AES-GCM", false},
diff --git a/util/fipstools/acvp/acvptool/subprocess/xts.go b/util/fipstools/acvp/acvptool/subprocess/xts.go new file mode 100644 index 0000000..e3546a3 --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/xts.go
@@ -0,0 +1,148 @@ +// Copyright (c) 2021, Google Inc. +// +// 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. + +package subprocess + +import ( + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" +) + +// The following structures reflect the JSON of ACVP hash tests. See +// https://pages.nist.gov/ACVP/draft-celi-acvp-symmetric.html + +type xtsTestVectorSet struct { + Groups []xtsTestGroup `json:"testGroups"` +} + +type xtsTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + Direction string `json:"direction"` + KeyLen int `json:"keyLen"` + PayloadLen int `json:"payloadLen"` + Tests []struct { + ID uint64 `json:"tcId"` + KeyHex string `json:"key"` + PlaintextHex string `json:"pt"` + CiphertextHex string `json:"ct"` + SectorNum *uint64 `json:"sequenceNumber"` + TweakHex *string `json:"tweakValue"` + } `json:"tests"` +} + +type xtsTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []xtsTestResponse `json:"tests"` +} + +type xtsTestResponse struct { + ID uint64 `json:"tcId"` + PlaintextHex string `json:"pt,omitempty"` + CiphertextHex string `json:"ct,omitempty"` +} + +// xts implements an ACVP algorithm by making requests to the subprocess to +// encrypt/decrypt with AES-XTS. +type xts struct{} + +func (h *xts) Process(vectorSet []byte, m Transactable) (interface{}, error) { + var parsed xtsTestVectorSet + if err := json.Unmarshal(vectorSet, &parsed); err != nil { + return nil, err + } + + var ret []xtsTestGroupResponse + for _, group := range parsed.Groups { + response := xtsTestGroupResponse{ + ID: group.ID, + } + + if group.Type != "AFT" { + return nil, fmt.Errorf("unknown XTS test type %q", group.Type) + } + + var decrypt bool + switch group.Direction { + case "encrypt": + decrypt = false + case "decrypt": + decrypt = true + default: + return nil, fmt.Errorf("unknown XTS direction %q", group.Direction) + } + + funcName := "AES-XTS/" + group.Direction + + for _, test := range group.Tests { + if group.KeyLen != len(test.KeyHex)*4/2 { + return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a key length of %d (remember that XTS keys are twice the length of the underlying key size)", group.ID, test.ID, len(test.KeyHex), group.KeyLen) + } + key, err := hex.DecodeString(test.KeyHex) + if err != nil { + return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err) + } + + var tweak [16]byte + if test.TweakHex != nil { + t, err := hex.DecodeString(*test.TweakHex) + if err != nil { + return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err) + } + if len(t) != len(tweak) { + return nil, fmt.Errorf("wrong tweak length (%d bytes) in test case %d/%d", len(t), group.ID, test.ID) + } + copy(tweak[:], t) + } else if test.SectorNum != nil { + // Sector numbers (or "sequence numbers", as NIST calls them) are turned + // into tweak values by encoding them in little-endian form. See IEEE + // 1619-2007, section 5.1. + binary.LittleEndian.PutUint64(tweak[:8], *test.SectorNum) + } else { + return nil, fmt.Errorf("neither sector number nor explicit tweak in test case %d/%d", group.ID, test.ID) + } + + var msg []byte + if decrypt { + msg, err = hex.DecodeString(test.CiphertextHex) + } else { + msg, err = hex.DecodeString(test.PlaintextHex) + } + + if err != nil { + return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err) + } + + result, err := m.Transact(funcName, 1, key, msg, tweak[:]) + if err != nil { + return nil, fmt.Errorf("submodule failed on test case %d/%d: %s", group.ID, test.ID, err) + } + + testResponse := xtsTestResponse{ID: test.ID} + if decrypt { + testResponse.PlaintextHex = hex.EncodeToString(result[0]) + } else { + testResponse.CiphertextHex = hex.EncodeToString(result[0]) + } + + response.Tests = append(response.Tests, testResponse) + } + + ret = append(ret, response) + } + + return ret, nil +}
diff --git a/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-XTS.bz2 b/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-XTS.bz2 new file mode 100644 index 0000000..a197e27 --- /dev/null +++ b/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-XTS.bz2 Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json index 842f3ff..f0bafd8 100644 --- a/util/fipstools/acvp/acvptool/test/tests.json +++ b/util/fipstools/acvp/acvptool/test/tests.json
@@ -6,6 +6,7 @@ {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-GCM.bz2", "Out": "expected/ACVP-AES-GCM.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-KW.bz2", "Out": "expected/ACVP-AES-KW.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-KWP.bz2", "Out": "expected/ACVP-AES-KWP.bz2"}, +{"Wrapper": "testmodulewrapper", "In": "vectors/ACVP-AES-XTS.bz2", "Out": "expected/ACVP-AES-XTS.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-TDES-CBC.bz2", "Out": "expected/ACVP-TDES-CBC.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-TDES-ECB.bz2", "Out": "expected/ACVP-TDES-ECB.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/CMAC-AES.bz2", "Out": "expected/CMAC-AES.bz2"},
diff --git a/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-XTS.bz2 b/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-XTS.bz2 new file mode 100644 index 0000000..43bd027 --- /dev/null +++ b/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-XTS.bz2 Binary files differ
diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go index 284b5d3..00c32ab 100644 --- a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go +++ b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
@@ -6,6 +6,7 @@ import ( "bytes" + "crypto/aes" "crypto/hmac" "crypto/rand" "crypto/sha256" @@ -14,11 +15,15 @@ "fmt" "io" "os" + + "golang.org/x/crypto/xts" ) var handlers = map[string]func([][]byte) error{ - "getConfig": getConfig, - "KDF-counter": kdfCounter, + "getConfig": getConfig, + "KDF-counter": kdfCounter, + "AES-XTS/encrypt": xtsEncrypt, + "AES-XTS/decrypt": xtsDecrypt, } func getConfig(args [][]byte) error { @@ -47,6 +52,23 @@ 32 ] }] + }, { + "algorithm": "ACVP-AES-XTS", + "revision": "1.0", + "direction": [ + "encrypt", + "decrypt" + ], + "keyLen": [ + 128, + 256 + ], + "payloadLen": [ + 1024 + ], + "tweakMode": [ + "number" + ] } ]`)) } @@ -122,6 +144,50 @@ 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) +} + const ( maxArgs = 8 maxArgLength = 1 << 20