-
Notifications
You must be signed in to change notification settings - Fork 183
Add ACVP Support for KAS-ECC #3010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
9f698b3
d4d9f42
85373fa
e4243eb
d1a1e25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,243 @@ | ||||||||||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||||||||||
| // SPDX-License-Identifier: Apache-2.0 OR ISC | ||||||||||
|
|
||||||||||
| package subprocess | ||||||||||
|
|
||||||||||
| import ( | ||||||||||
| "bytes" | ||||||||||
| "encoding/binary" | ||||||||||
| "encoding/hex" | ||||||||||
| "encoding/json" | ||||||||||
| "fmt" | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| type kasEccVectorSet struct { | ||||||||||
| Groups []kasEccTestGroup `json:"testGroups"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| type kdfConfiguration struct { | ||||||||||
| KdfType string `json:"kdfType"` | ||||||||||
| AuxFunction string `json:"auxFunction"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| type kdfParameter struct { | ||||||||||
| KdfType string `json:"kdfType"` | ||||||||||
| AlgorithmId string `json:"algorithmId"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| type kasEccTestGroup struct { | ||||||||||
| ID uint64 `json:"tgId"` | ||||||||||
| Type string `json:"testType"` | ||||||||||
| Curve string `json:"domainParameterGenerationMode"` | ||||||||||
| Role string `json:"kasRole"` | ||||||||||
| Scheme string `json:"scheme"` | ||||||||||
| KdfConfiguration kdfConfiguration `json:"kdfConfiguration"` | ||||||||||
| L uint32 `json:"l"` | ||||||||||
| IutId string `json:"iutId"` | ||||||||||
| ServerId string `json:"serverId"` | ||||||||||
| Tests []kasEccTest `json:"tests"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| type kasEccTest struct { | ||||||||||
| ID uint64 `json:"tcId"` | ||||||||||
|
|
||||||||||
| EphemeralXHex hexEncodedByteString `json:"ephemeralPublicServerX"` | ||||||||||
| EphemeralYHex hexEncodedByteString `json:"ephemeralPublicServerY"` | ||||||||||
| EphemeralPrivateKeyHex hexEncodedByteString `json:"ephemeralPrivateIut"` | ||||||||||
| EphemeralPublicIutXHex hexEncodedByteString `json:"ephemeralPublicIutX"` | ||||||||||
| EphemeralPublicIutYHex hexEncodedByteString `json:"ephemeralPublicIutY"` | ||||||||||
| KdfParameter kdfParameter `json:"kdfParameter"` | ||||||||||
| ExpectedOutput hexEncodedByteString `json:"dkm"` | ||||||||||
|
|
||||||||||
| StaticXHex hexEncodedByteString `json:"staticPublicServerX"` | ||||||||||
| StaticYHex hexEncodedByteString `json:"staticPublicServerY"` | ||||||||||
| StaticPrivateKeyHex hexEncodedByteString `json:"staticPrivateIut"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| type kasEccTestGroupResponse struct { | ||||||||||
| ID uint64 `json:"tgId"` | ||||||||||
| Tests []kasEccTestResponse `json:"tests"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| type kasEccResponse struct { | ||||||||||
| VsId uint64 `json:"vsId,omitempty"` | ||||||||||
| Algorithm string `json:"algorithm,omitempty"` | ||||||||||
| Revision string `json:"revision,omitempty"` | ||||||||||
| IsSample bool `json:"isSample,omitempty"` | ||||||||||
| TestGroups []kasEccTestGroupResponse `json:"testGroups"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| type kasEccTestResponse struct { | ||||||||||
| ID uint64 `json:"tcId"` | ||||||||||
|
|
||||||||||
| EphemeralXHex string `json:"ephemeralPublicIutX,omitempty"` | ||||||||||
| EphemeralYHex string `json:"ephemeralPublicIutY,omitempty"` | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Then you don't need to call |
||||||||||
|
|
||||||||||
| Dkm string `json:"dkm,omitempty"` | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| Passed *bool `json:"testPassed,omitempty"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| type kasEcc struct{} | ||||||||||
|
|
||||||||||
| func (k *kasEcc) Process(vectorSet []byte, m Transactable) (interface{}, error) { | ||||||||||
| var parsed kasEccVectorSet | ||||||||||
| if err := json.Unmarshal(vectorSet, &parsed); err != nil { | ||||||||||
| return nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // See https://pages.nist.gov/ACVP/draft-fussell-acvp-kas-ecc.html#name-test-vectors | ||||||||||
| var ret []kasEccTestGroupResponse | ||||||||||
| for _, group := range parsed.Groups { | ||||||||||
| response := kasEccTestGroupResponse{ | ||||||||||
| ID: group.ID, | ||||||||||
| } | ||||||||||
|
|
||||||||||
| var privateKeyGiven bool | ||||||||||
| switch group.Type { | ||||||||||
| case "AFT": | ||||||||||
| privateKeyGiven = false | ||||||||||
| case "VAL": | ||||||||||
| privateKeyGiven = true | ||||||||||
| default: | ||||||||||
| return nil, fmt.Errorf("unknown test type %q", group.Type) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| switch group.Curve { | ||||||||||
| case "P-224", "P-256", "P-384", "P-521": | ||||||||||
| default: | ||||||||||
| return nil, fmt.Errorf("unknown curve %q", group.Curve) | ||||||||||
| } | ||||||||||
|
nhatnghiho marked this conversation as resolved.
|
||||||||||
|
|
||||||||||
| switch group.Role { | ||||||||||
| case "initiator", "responder": | ||||||||||
| default: | ||||||||||
| return nil, fmt.Errorf("unknown role %q", group.Role) | ||||||||||
| } | ||||||||||
|
nhatnghiho marked this conversation as resolved.
|
||||||||||
|
|
||||||||||
| var useOnePassNameFields bool | ||||||||||
| switch group.Scheme { | ||||||||||
| case "ephemeralUnified": | ||||||||||
| case "onePassDh": | ||||||||||
| useOnePassNameFields = true | ||||||||||
| default: | ||||||||||
| return nil, fmt.Errorf("unknown scheme %q", group.Scheme) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| switch group.KdfConfiguration.KdfType { | ||||||||||
| case "oneStep": | ||||||||||
| default: | ||||||||||
| return nil, fmt.Errorf("unknown KDF type %q", group.KdfConfiguration.KdfType) | ||||||||||
| } | ||||||||||
|
nhatnghiho marked this conversation as resolved.
|
||||||||||
|
|
||||||||||
| method := "KAS-ECC/OneStep/" + group.Curve + "/" + group.KdfConfiguration.AuxFunction | ||||||||||
|
|
||||||||||
| for _, test := range group.Tests { | ||||||||||
| var peerX, peerY, privateKey hexEncodedByteString | ||||||||||
| if useOnePassNameFields { | ||||||||||
| if group.Role == "initiator" { | ||||||||||
| peerX, peerY, privateKey = test.StaticXHex, test.StaticYHex, test.StaticPrivateKeyHex | ||||||||||
| } else { | ||||||||||
| peerX, peerY, privateKey = test.EphemeralXHex, test.EphemeralYHex, test.StaticPrivateKeyHex | ||||||||||
| } | ||||||||||
| } else { | ||||||||||
| peerX, peerY, privateKey = test.EphemeralXHex, test.EphemeralYHex, test.EphemeralPrivateKeyHex | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if len(peerX) == 0 || len(peerY) == 0 { | ||||||||||
| return nil, fmt.Errorf("%d/%d is missing peer's point", group.ID, test.ID) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (len(privateKey) != 0) != privateKeyGiven { | ||||||||||
| return nil, fmt.Errorf("%d/%d incorrect private key presence", group.ID, test.ID) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| var outLenBytes [4]byte | ||||||||||
| NativeEndian.PutUint32(outLenBytes[:], group.L/8) // Convert bits to bytes | ||||||||||
|
|
||||||||||
| // Build fixedInfo: algorithmId || l || uPartyInfo || vPartyInfo | ||||||||||
| // uPartyInfo = uPartyId || ephemeralKey (when available) | ||||||||||
| // vPartyInfo = vPartyId || ephemeralKey (when available) | ||||||||||
| algorithmId, err := hex.DecodeString(test.KdfParameter.AlgorithmId) | ||||||||||
| if err != nil { | ||||||||||
| return nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| var lBytes [4]byte | ||||||||||
| binary.BigEndian.PutUint32(lBytes[:], group.L) | ||||||||||
|
|
||||||||||
| iutId, err := hex.DecodeString(group.IutId) | ||||||||||
| if err != nil { | ||||||||||
| return nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| serverId, err := hex.DecodeString(group.ServerId) | ||||||||||
| if err != nil { | ||||||||||
| return nil, err | ||||||||||
| } | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused these aren't hex values so why are you calling
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As counter-intuitive as it sounds, they are hex values (as noted in the JSON specifications here). I'll change their type from |
||||||||||
|
|
||||||||||
| fixedInfo := append([]byte{}, algorithmId...) | ||||||||||
| fixedInfo = append(fixedInfo, lBytes[:]...) | ||||||||||
|
|
||||||||||
| if group.Role == "initiator" { | ||||||||||
| // IUT is party U (initiator), Server is party V (responder) | ||||||||||
| fixedInfo = append(fixedInfo, iutId...) | ||||||||||
| if !useOnePassNameFields { | ||||||||||
| // ephemeralUnified: IUT has ephemeral key | ||||||||||
| if privateKeyGiven { | ||||||||||
| // VAL mode: use provided public keyreturn nil, err | ||||||||||
| fixedInfo = append(fixedInfo, test.EphemeralPublicIutXHex...) | ||||||||||
| fixedInfo = append(fixedInfo, test.EphemeralPublicIutYHex...) | ||||||||||
| } | ||||||||||
| } | ||||||||||
| fixedInfo = append(fixedInfo, serverId...) | ||||||||||
| fixedInfo = append(fixedInfo, peerX...) | ||||||||||
| fixedInfo = append(fixedInfo, peerY...) | ||||||||||
| } else { | ||||||||||
| // Server is U party (initiator), IUT is V party (responder) | ||||||||||
| fixedInfo = append(fixedInfo, serverId...) | ||||||||||
| fixedInfo = append(fixedInfo, peerX...) | ||||||||||
| fixedInfo = append(fixedInfo, peerY...) | ||||||||||
| fixedInfo = append(fixedInfo, iutId...) | ||||||||||
| if !useOnePassNameFields { | ||||||||||
| // ephemeralUnified: IUT has ephemeral key | ||||||||||
| if privateKeyGiven { | ||||||||||
| // VAL mode: use provided public key | ||||||||||
| fixedInfo = append(fixedInfo, test.EphemeralPublicIutXHex...) | ||||||||||
| fixedInfo = append(fixedInfo, test.EphemeralPublicIutYHex...) | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if privateKeyGiven { | ||||||||||
| result, err := m.Transact(method, 3, peerX, peerY, privateKey, fixedInfo, outLenBytes[:], nil) | ||||||||||
| if err != nil { | ||||||||||
| return nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| ok := bytes.Equal(result[2], test.ExpectedOutput) | ||||||||||
| response.Tests = append(response.Tests, kasEccTestResponse{ | ||||||||||
| ID: test.ID, | ||||||||||
| Passed: &ok, | ||||||||||
| }) | ||||||||||
| } else { | ||||||||||
| result, err := m.Transact(method, 3, peerX, peerY, nil, fixedInfo, outLenBytes[:], nil) | ||||||||||
| if err != nil { | ||||||||||
| return nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| testResponse := kasEccTestResponse{ | ||||||||||
| ID: test.ID, | ||||||||||
| EphemeralXHex: hex.EncodeToString(result[0]), | ||||||||||
| EphemeralYHex: hex.EncodeToString(result[1]), | ||||||||||
| Dkm: hex.EncodeToString(result[2]), | ||||||||||
| } | ||||||||||
|
|
||||||||||
| response.Tests = append(response.Tests, testResponse) | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| ret = append(ret, response) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return ret, nil | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -1335,6 +1335,31 @@ static bool GetConfig(const Span<const uint8_t> args[], | |||||
| "P-521" | ||||||
| ] | ||||||
| }, | ||||||
| { | ||||||
| "algorithm": "KAS-ECC", | ||||||
| "revision": "Sp800-56Ar3", | ||||||
| "function": ["fullVal"], | ||||||
| "iutId": ["initiator", "responder"], | ||||||
| "scheme": { | ||||||
| "ephemeralUnified": { | ||||||
| "kasRole": ["initiator", "responder"], | ||||||
| "kdfMethods": { | ||||||
| "oneStepKdf": { | ||||||
| "auxFunctions": [ | ||||||
| {"auxFunctionName": "SHA2-224"}, | ||||||
| {"auxFunctionName": "SHA2-256"}, | ||||||
| {"auxFunctionName": "SHA2-384"}, | ||||||
| {"auxFunctionName": "SHA2-512"} | ||||||
| ], | ||||||
| "fixedInfoPattern": "algorithmId", | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't match the |
||||||
| "encoding": ["concatenation"] | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| }, | ||||||
| "domainParameterGenerationMethods": ["P-224", "P-256", "P-384", "P-521"], | ||||||
| "l": 2048 | ||||||
| }, | ||||||
| { | ||||||
| "algorithm": "KAS-FFC-SSC", | ||||||
| "revision": "Sp800-56Ar3", | ||||||
|
|
@@ -2869,6 +2894,72 @@ static bool ECDH(const Span<const uint8_t> args[], ReplyCallback write_reply) { | |||||
| return write_reply({BIGNUMBytes(x.get()), BIGNUMBytes(y.get()), output}); | ||||||
| } | ||||||
|
|
||||||
| template <int Nid, const EVP_MD *(MDFunc)()> | ||||||
| static bool ECDH_SSKDF(const Span<const uint8_t> args[], | ||||||
| ReplyCallback write_reply) { | ||||||
| bssl::UniquePtr<BIGNUM> their_x(BytesToBIGNUM(args[0])); | ||||||
| bssl::UniquePtr<BIGNUM> their_y(BytesToBIGNUM(args[1])); | ||||||
| const Span<const uint8_t> private_key = args[2]; | ||||||
| const Span<const uint8_t> info = args[3]; | ||||||
| const Span<const uint8_t> out_len_bytes = args[4]; | ||||||
|
|
||||||
| uint32_t out_len; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: variable 'out_len' is not initialized [cppcoreguidelines-init-variables]
Suggested change
|
||||||
| memcpy(&out_len, out_len_bytes.data(), sizeof(out_len)); | ||||||
|
|
||||||
| // Step 1: ECDH - compute shared secret Z | ||||||
| bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new_by_curve_name(Nid)); | ||||||
| bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new()); | ||||||
| const EC_GROUP *group = EC_KEY_get0_group(ec_key.get()); | ||||||
|
|
||||||
| bssl::UniquePtr<EC_POINT> their_point(EC_POINT_new(group)); | ||||||
| if (!EC_POINT_set_affine_coordinates_GFp( | ||||||
| group, their_point.get(), their_x.get(), their_y.get(), ctx.get())) { | ||||||
| LOG_ERROR("Invalid peer point for KAS-ECC.\n"); | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| if (!private_key.empty()) { | ||||||
| bssl::UniquePtr<BIGNUM> our_k(BytesToBIGNUM(private_key)); | ||||||
| if (!EC_KEY_set_private_key(ec_key.get(), our_k.get())) { | ||||||
| return false; | ||||||
| } | ||||||
| bssl::UniquePtr<EC_POINT> our_pub(EC_POINT_new(group)); | ||||||
| if (!EC_POINT_mul(group, our_pub.get(), our_k.get(), nullptr, nullptr, | ||||||
| ctx.get()) || | ||||||
| !EC_KEY_set_public_key(ec_key.get(), our_pub.get())) { | ||||||
| return false; | ||||||
| } | ||||||
| } else if (!EC_KEY_generate_key_fips(ec_key.get())) { | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| std::vector<uint8_t> z(EC_MAX_BYTES + 1); | ||||||
| int z_len = ECDH_compute_key(z.data(), z.size(), their_point.get(), | ||||||
| ec_key.get(), nullptr); | ||||||
| if (z_len < 0 || static_cast<size_t>(z_len) == z.size()) { | ||||||
| return false; | ||||||
| } | ||||||
| z.resize(z_len); | ||||||
|
|
||||||
| // Step 2: One-Step KDF (SSKDF with digest) | ||||||
| std::vector<uint8_t> output(out_len); | ||||||
| if (!::SSKDF_digest(output.data(), out_len, MDFunc(), z.data(), z.size(), | ||||||
| info.data(), info.size())) { | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| // Return public key and DKM | ||||||
| const EC_POINT *pub = EC_KEY_get0_public_key(ec_key.get()); | ||||||
| bssl::UniquePtr<BIGNUM> x(BN_new()); | ||||||
| bssl::UniquePtr<BIGNUM> y(BN_new()); | ||||||
| if (!EC_POINT_get_affine_coordinates_GFp(group, pub, x.get(), y.get(), | ||||||
| ctx.get())) { | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| return write_reply({BIGNUMBytes(x.get()), BIGNUMBytes(y.get()), output}); | ||||||
| } | ||||||
|
|
||||||
| static bool FFDH(const Span<const uint8_t> args[], ReplyCallback write_reply) { | ||||||
| bssl::UniquePtr<BIGNUM> p(BytesToBIGNUM(args[0])); | ||||||
| bssl::UniquePtr<BIGNUM> q(BytesToBIGNUM(args[1])); | ||||||
|
|
@@ -3647,6 +3738,10 @@ static struct { | |||||
| {"ECDH/P-256", 3, ECDH<NID_X9_62_prime256v1>}, | ||||||
| {"ECDH/P-384", 3, ECDH<NID_secp384r1>}, | ||||||
| {"ECDH/P-521", 3, ECDH<NID_secp521r1>}, | ||||||
| {"KAS-ECC/OneStep/P-224/SHA2-384", 6, | ||||||
| ECDH_SSKDF<NID_secp224r1, EVP_sha384>}, | ||||||
| {"KAS-ECC/OneStep/P-384/SHA2-384", 6, | ||||||
| ECDH_SSKDF<NID_secp384r1, EVP_sha384>}, | ||||||
| {"FFDH", 6, FFDH}, | ||||||
| {"PBKDF", 5, PBKDF}, | ||||||
| {"KDA/HKDF/SHA-1", 4, HKDF<EVP_sha1>}, | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.