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