From e64d468c558569135cd9cad27f3618bf11aab66e Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Fri, 16 Oct 2026 17:03:09 +0000 Subject: [PATCH] feat(test-server): build the Raw ECDH and AWS KMS ECDH keyrings Map the commons model's RawEcdh and AwsKmsEcdh keyring configs to the Material Providers Library's CreateRawEcdhKeyring and CreateAwsKmsEcdhKeyring, and validate that each key agreement scheme sets exactly one member. The KMS ECDH keyring's client is in the region of its sender (static) or recipient (discovery) key. --- .../server/config/ConfigValidator.java | 60 +++++++++++- .../server/config/EsdkClientFactory.java | 98 ++++++++++++++++++- 2 files changed, 154 insertions(+), 4 deletions(-) diff --git a/test-server/server/src/main/java/aws/cryptography/esdk/testserver/server/config/ConfigValidator.java b/test-server/server/src/main/java/aws/cryptography/esdk/testserver/server/config/ConfigValidator.java index 5c9f3bce..3128a2c5 100644 --- a/test-server/server/src/main/java/aws/cryptography/esdk/testserver/server/config/ConfigValidator.java +++ b/test-server/server/src/main/java/aws/cryptography/esdk/testserver/server/config/ConfigValidator.java @@ -6,7 +6,9 @@ import aws.cryptography.esdk.testserver.server.model.ESDKClientConfig; import aws.cryptography.esdk.testserver.server.model.ESDKClientError; import aws.cryptography.esdk.testserver.server.model.Keyring; +import aws.cryptography.esdk.testserver.server.model.KmsEcdhKeyAgreementScheme; import aws.cryptography.esdk.testserver.server.model.MultiKeyringConfig; +import aws.cryptography.esdk.testserver.server.model.RawEcdhKeyAgreementScheme; import aws.cryptography.esdk.testserver.server.model.RequiredEncryptionContextCmmConfig; /** @@ -19,8 +21,9 @@ * validated, recursing through the recursive variants to any nesting depth * (Requirement 2.5): {@link CryptographicMaterialsManager} (Default / * RequiredEncryptionContext / Caching, where Caching and - * RequiredEncryptionContext wrap another CMM) and {@link Keyring} (whose Multi - * variant contains child keyrings and an optional generator keyring). + * RequiredEncryptionContext wrap another CMM), {@link Keyring} (whose Multi + * variant contains child keyrings and an optional generator keyring), and the + * Raw ECDH and AWS KMS ECDH key agreement schemes. * *

When a polymorphic shape has zero, or two or more, variant members set, the * request is rejected with an {@link ESDKClientError} before the @@ -121,9 +124,21 @@ public void validateKeyring(Keyring keyring) { if (keyring.getMulti() != null) { set++; } + if (keyring.getRawEcdh() != null) { + set++; + } + if (keyring.getAwsKmsEcdh() != null) { + set++; + } if (set != 1) { throw error("Keyring", set, keyringVariants()); } + if (keyring.getRawEcdh() != null) { + validateRawEcdhScheme(keyring.getRawEcdh().getKeyAgreementScheme()); + } + if (keyring.getAwsKmsEcdh() != null) { + validateKmsEcdhScheme(keyring.getAwsKmsEcdh().getKeyAgreementScheme()); + } MultiKeyringConfig multi = keyring.getMulti(); if (multi != null) { @@ -136,9 +151,48 @@ public void validateKeyring(Keyring keyring) { } } + private static void validateRawEcdhScheme(RawEcdhKeyAgreementScheme scheme) { + String variants = "RawPrivateKeyToStaticPublicKey, EphemeralPrivateKeyToStaticPublicKey, " + + "PublicKeyDiscovery"; + if (scheme == null) { + throw error("RawEcdhKeyAgreementScheme", 0, variants); + } + int set = 0; + if (scheme.getRawPrivateKeyToStaticPublicKey() != null) { + set++; + } + if (scheme.getEphemeralPrivateKeyToStaticPublicKey() != null) { + set++; + } + if (scheme.getPublicKeyDiscovery() != null) { + set++; + } + if (set != 1) { + throw error("RawEcdhKeyAgreementScheme", set, variants); + } + } + + private static void validateKmsEcdhScheme(KmsEcdhKeyAgreementScheme scheme) { + String variants = "KmsPrivateKeyToStaticPublicKey, KmsPublicKeyDiscovery"; + if (scheme == null) { + throw error("KmsEcdhKeyAgreementScheme", 0, variants); + } + int set = 0; + if (scheme.getKmsPrivateKeyToStaticPublicKey() != null) { + set++; + } + if (scheme.getKmsPublicKeyDiscovery() != null) { + set++; + } + if (set != 1) { + throw error("KmsEcdhKeyAgreementScheme", set, variants); + } + } + private static String keyringVariants() { return "AwsKms, AwsKmsMrk, AwsKmsMultiKeyring, AwsKmsMrkMultiKeyring, " - + "AwsKmsDiscovery, AwsKmsMrkDiscovery, AwsKmsRsa, AwsKmsHierarchical, RawAes, RawRsa, Multi"; + + "AwsKmsDiscovery, AwsKmsMrkDiscovery, AwsKmsRsa, AwsKmsEcdh, AwsKmsHierarchical, " + + "RawAes, RawRsa, RawEcdh, Multi"; } private static ESDKClientError error(String shape, int set, String variants) { diff --git a/test-server/server/src/main/java/aws/cryptography/esdk/testserver/server/config/EsdkClientFactory.java b/test-server/server/src/main/java/aws/cryptography/esdk/testserver/server/config/EsdkClientFactory.java index 94b2f797..1b25f249 100644 --- a/test-server/server/src/main/java/aws/cryptography/esdk/testserver/server/config/EsdkClientFactory.java +++ b/test-server/server/src/main/java/aws/cryptography/esdk/testserver/server/config/EsdkClientFactory.java @@ -1,6 +1,7 @@ package aws.cryptography.esdk.testserver.server.config; import aws.cryptography.esdk.testserver.server.model.AwsKmsDiscoveryKeyringConfig; +import aws.cryptography.esdk.testserver.server.model.AwsKmsEcdhKeyringConfig; import aws.cryptography.esdk.testserver.server.model.AwsKmsHierarchicalKeyringConfig; import aws.cryptography.esdk.testserver.server.model.AwsKmsKeyringConfig; import aws.cryptography.esdk.testserver.server.model.AwsKmsMrkKeyringConfig; @@ -11,11 +12,15 @@ import aws.cryptography.esdk.testserver.server.model.CachingCmmConfig; import aws.cryptography.esdk.testserver.server.model.CryptographicMaterialsManager; import aws.cryptography.esdk.testserver.server.model.DefaultCmmConfig; +import aws.cryptography.esdk.testserver.server.model.EcdhCurveSpec; import aws.cryptography.esdk.testserver.server.model.ESDKClientConfig; import aws.cryptography.esdk.testserver.server.model.Keyring; +import aws.cryptography.esdk.testserver.server.model.KmsEcdhKeyAgreementScheme; import aws.cryptography.esdk.testserver.server.model.KmsRsaEncryptionAlgorithm; import aws.cryptography.esdk.testserver.server.model.MultiKeyringConfig; import aws.cryptography.esdk.testserver.server.model.RawAesKeyringConfig; +import aws.cryptography.esdk.testserver.server.model.RawEcdhKeyAgreementScheme; +import aws.cryptography.esdk.testserver.server.model.RawEcdhKeyringConfig; import aws.cryptography.esdk.testserver.server.model.RawRsaKeyringConfig; import aws.cryptography.esdk.testserver.server.model.RequiredEncryptionContextCmmConfig; import aws.cryptography.esdk.testserver.server.registry.EsdkClient; @@ -39,6 +44,7 @@ import software.amazon.cryptography.materialproviders.MaterialProviders; import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; import software.amazon.cryptography.materialproviders.model.CreateAwsKmsDiscoveryKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsEcdhKeyringInput; import software.amazon.cryptography.materialproviders.model.CreateAwsKmsHierarchicalKeyringInput; import software.amazon.cryptography.materialproviders.model.CreateAwsKmsKeyringInput; import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkKeyringInput; @@ -49,10 +55,19 @@ import software.amazon.cryptography.materialproviders.model.CreateDefaultCryptographicMaterialsManagerInput; import software.amazon.cryptography.materialproviders.model.CreateMultiKeyringInput; import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateRawEcdhKeyringInput; import software.amazon.cryptography.materialproviders.model.CreateRawRsaKeyringInput; import software.amazon.cryptography.materialproviders.model.CreateRequiredEncryptionContextCMMInput; +import software.amazon.cryptography.materialproviders.model.EphemeralPrivateKeyToStaticPublicKeyInput; +import software.amazon.cryptography.materialproviders.model.KmsEcdhStaticConfigurations; +import software.amazon.cryptography.materialproviders.model.KmsPrivateKeyToStaticPublicKeyInput; +import software.amazon.cryptography.materialproviders.model.KmsPublicKeyDiscoveryInput; import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; import software.amazon.cryptography.materialproviders.model.PaddingScheme; +import software.amazon.cryptography.materialproviders.model.PublicKeyDiscoveryInput; +import software.amazon.cryptography.materialproviders.model.RawEcdhStaticConfigurations; +import software.amazon.cryptography.materialproviders.model.RawPrivateKeyToStaticPublicKeyInput; +import software.amazon.cryptography.primitives.model.ECDHCurveSpec; /** * Translates a validated {@link ESDKClientConfig} into a {@link RealEsdkClient} @@ -80,7 +95,10 @@ * network call — * the KMS client is built eagerly but is not invoked — so {@code CreateClient} * stays offline; the real AWS KMS calls happen only on {@code Encrypt}/{@code - * Decrypt} (Requirements 14.1, 14.3, 14.4, 14.14). The single exception is the + * Decrypt} (Requirements 14.1, 14.3, 14.4, 14.14). The Raw ECDH and AWS KMS ECDH + * keyrings are wired as well. The exceptions are the AWS KMS ECDH keyring when its + * static configuration omits the sender's public key (fetched via + * {@code kms:GetPublicKey}), and the * {@code AwsKmsRsa} keyring when the modeled config omits the RSA public key: in * that case the factory fetches it once via {@code kms:GetPublicKey} at * construction (a network call the design explicitly permits at @@ -154,6 +172,8 @@ private IKeyring buildKeyring(Keyring keyring) { AwsKmsDiscoveryKeyringConfig awsKmsDiscovery = keyring.getAwsKmsDiscovery(); AwsKmsMrkDiscoveryKeyringConfig awsKmsMrkDiscovery = keyring.getAwsKmsMrkDiscovery(); AwsKmsHierarchicalKeyringConfig awsKmsHierarchical = keyring.getAwsKmsHierarchical(); + RawEcdhKeyringConfig rawEcdh = keyring.getRawEcdh(); + AwsKmsEcdhKeyringConfig awsKmsEcdh = keyring.getAwsKmsEcdh(); if (rawAes != null) { return materialProviders.CreateRawAesKeyring( @@ -253,9 +273,85 @@ private IKeyring buildKeyring(Keyring keyring) { if (awsKmsHierarchical != null) { return buildAwsKmsHierarchicalKeyring(awsKmsHierarchical); } + if (rawEcdh != null) { + return buildRawEcdhKeyring(rawEcdh); + } + if (awsKmsEcdh != null) { + return buildAwsKmsEcdhKeyring(awsKmsEcdh); + } throw new IllegalArgumentException("Keyring had no variant member set"); } + /** Build the Raw ECDH keyring from its key agreement scheme and curve. */ + private IKeyring buildRawEcdhKeyring(RawEcdhKeyringConfig config) { + RawEcdhKeyAgreementScheme scheme = config.getKeyAgreementScheme(); + RawEcdhStaticConfigurations.Builder agreement = RawEcdhStaticConfigurations.builder(); + if (scheme.getRawPrivateKeyToStaticPublicKey() != null) { + agreement.RawPrivateKeyToStaticPublicKey(RawPrivateKeyToStaticPublicKeyInput.builder() + .senderStaticPrivateKey(scheme.getRawPrivateKeyToStaticPublicKey().getSenderStaticPrivateKey()) + .recipientPublicKey(scheme.getRawPrivateKeyToStaticPublicKey().getRecipientPublicKey()) + .build()); + } else if (scheme.getEphemeralPrivateKeyToStaticPublicKey() != null) { + agreement.EphemeralPrivateKeyToStaticPublicKey(EphemeralPrivateKeyToStaticPublicKeyInput.builder() + .recipientPublicKey(scheme.getEphemeralPrivateKeyToStaticPublicKey().getRecipientPublicKey()) + .build()); + } else { + agreement.PublicKeyDiscovery(PublicKeyDiscoveryInput.builder() + .recipientStaticPrivateKey(scheme.getPublicKeyDiscovery().getRecipientStaticPrivateKey()) + .build()); + } + return materialProviders.CreateRawEcdhKeyring(CreateRawEcdhKeyringInput.builder() + .KeyAgreementScheme(agreement.build()) + .curveSpec(toCurveSpec(config.getCurveSpec())) + .build()); + } + + /** + * Build the AWS KMS ECDH keyring. The KMS client is in the region of the + * sender's (static) or recipient's (discovery) KMS key; construction makes no + * network call unless the static configuration omits the sender's public key, + * which the MPL then fetches with {@code kms:GetPublicKey}. + */ + private IKeyring buildAwsKmsEcdhKeyring(AwsKmsEcdhKeyringConfig config) { + KmsEcdhKeyAgreementScheme scheme = config.getKeyAgreementScheme(); + KmsEcdhStaticConfigurations.Builder agreement = KmsEcdhStaticConfigurations.builder(); + String kmsKeyId; + if (scheme.getKmsPrivateKeyToStaticPublicKey() != null) { + var staticConfig = scheme.getKmsPrivateKeyToStaticPublicKey(); + kmsKeyId = staticConfig.getSenderKmsIdentifier(); + KmsPrivateKeyToStaticPublicKeyInput.Builder input = KmsPrivateKeyToStaticPublicKeyInput.builder() + .senderKmsIdentifier(kmsKeyId) + .recipientPublicKey(staticConfig.getRecipientPublicKey()); + if (staticConfig.getSenderPublicKey() != null) { + input.senderPublicKey(staticConfig.getSenderPublicKey()); + } + agreement.KmsPrivateKeyToStaticPublicKey(input.build()); + } else { + kmsKeyId = scheme.getKmsPublicKeyDiscovery().getRecipientKmsIdentifier(); + agreement.KmsPublicKeyDiscovery(KmsPublicKeyDiscoveryInput.builder() + .recipientKmsIdentifier(kmsKeyId) + .build()); + } + CreateAwsKmsEcdhKeyringInput.Builder builder = CreateAwsKmsEcdhKeyringInput.builder() + .KeyAgreementScheme(agreement.build()) + .curveSpec(toCurveSpec(config.getCurveSpec())) + .kmsClient(kmsClientForKey(kmsKeyId)); + if (config.hasGrantTokens() && !config.getGrantTokens().isEmpty()) { + builder.grantTokens(new ArrayList<>(config.getGrantTokens())); + } + return materialProviders.CreateAwsKmsEcdhKeyring(builder.build()); + } + + private static ECDHCurveSpec toCurveSpec(EcdhCurveSpec curve) { + return switch (curve.getValue()) { + case "ECC_NIST_P256" -> ECDHCurveSpec.ECC_NIST_P256; + case "ECC_NIST_P384" -> ECDHCurveSpec.ECC_NIST_P384; + case "ECC_NIST_P521" -> ECDHCurveSpec.ECC_NIST_P521; + case "SM2" -> ECDHCurveSpec.SM2; + default -> throw new IllegalArgumentException("Unknown ECDH curve: " + curve.getValue()); + }; + } + /** * Build the AWS KMS RSA keyring (Requirement 14.3, 14.4). The keyring needs * the RSA public key bytes (for encrypt), the KMS key id/ARN and a KMS