Skip to content

Commit

Permalink
feat(Mutations-TODO): Some Terminal KMS Exceptions
Browse files Browse the repository at this point in the history
If the KMS Call, for mutating the Beacon, fails,
it MAY indicate the MPL Consumer does not have access to the
terminal KMS Key.

If the KMS call for verifying a terminal version fails,
it MAY indicate the MPL Consumer does not have access to the
terminal KMS Key.
  • Loading branch information
texastony committed Oct 1, 2024
1 parent 04c4345 commit 8f17d17
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,9 @@ module {:extern "software.amazon.cryptography.keystore.internaldafny.types" } Aw
| AlreadyExistsConditionFailed (
nameonly message: string
)
| KeyManagementException (
nameonly message: string
)
| KeyStorageException (
nameonly message: string
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,37 @@ structure KeyStoreException {
// Can be thrown by InitializeMutation & VersionKey
@error("client")
@retryable
@documentation("Operation was rejected due to a race with VersionKey. No items were changed. Retry operation when no other agent is Versioning this Branch Key ID.")
@documentation(
"Operation was rejected due to a race with VersionKey.
No items were changed.
Retry operation when no other agent is Versioning this Branch Key ID.")
structure VersionRaceException {
@required
message: String,
}

// This should be used very carefully.
// It is often better to simply return the KMS Exception,
// rather than obscuring it with this.
// However, in cases where the KMS response
// is invalid due to Client Side Validation,
// this MAY be a better error to throw than
// the generic local service exception.
// See https://github.com/smithy-lang/smithy-dafny/issues/614
@error("client")
@documentation("AWS KMS request was unsuccesful or response was invalid.")
structure KeyManagementException {
@required
message: String
}

@error("client")
@documentation("AWS KMS request was unsuccesful or response was invalid.")
structure ComAmazonawsKms {
error: com.amazonaws.kms#Error
}

union com.amazonaws.kms#Error (
AlreadyExistsException: com.amazonaws.kms#AlreadyExistsException
CloudHsmClusterInUseException: com.amazonaws.kms#CloudHsmClusterInUseException
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ module {:options "/functionSyntax:4" } KMSKeystoreOperations {
import KmsArn
import ErrorMessages = KeyStoreErrorMessages

type KmsError = e: Types.Error | (e.ComAmazonawsKms? || e.KeyManagementException?) witness *

function replaceRegion(arn : KMS.KeyIdType, region : KMS.RegionType) : KMS.KeyIdType
{
var parsed := ParseAwsKmsArn(arn);
Expand Down Expand Up @@ -204,7 +206,7 @@ module {:options "/functionSyntax:4" } KMSKeystoreOperations {
grantTokens: KMS.GrantTokenList,
kmsClient: KMS.IKMSClient
)
returns (res: Result<KMS.ReEncryptResponse, Types.Error>)
returns (res: Result<KMS.ReEncryptResponse, KmsError>)
requires
&& Structure.BranchKeyContext?(sourceEncryptionContext)
&& Structure.BranchKeyContext?(destinationEncryptionContext)
Expand Down Expand Up @@ -250,7 +252,7 @@ module {:options "/functionSyntax:4" } KMSKeystoreOperations {
{
:- Need(
KMS.IsValid_CiphertextType(ciphertext),
Types.KeyStoreException(
Types.KeyManagementException(
message := "Invalid KMS ciphertext.")
);

Expand All @@ -275,14 +277,14 @@ module {:options "/functionSyntax:4" } KMSKeystoreOperations {
&& reEncryptResponse.KeyId.Some?
&& reEncryptResponse.SourceKeyId.value == kmsKeyArn
&& reEncryptResponse.KeyId.value == kmsKeyArn,
Types.KeyStoreException(
message := "Invalid response from KMS ReEncrypt:: Invalid Key Id")
Types.KeyManagementException(
message := "Invalid response from AWS KMS ReEncrypt:: Invalid KMS Key Id")
);

:- Need(
&& reEncryptResponse.CiphertextBlob.Some?
&& KMS.IsValid_CiphertextType(reEncryptResponse.CiphertextBlob.value),
Types.KeyStoreException(
Types.KeyManagementException(
message := "Invalid response from AWS KMS ReEncrypt: Invalid ciphertext.")
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
include "../Model/AwsCryptographyKeyStoreAdminTypes.dfy"

module {:options "/functionSyntax:4" } MutationErrorRefinement {
import opened Wrappers
import Types = AwsCryptographyKeyStoreAdminTypes
import KeyStoreTypes = AwsCryptographyKeyStoreAdminTypes.AwsCryptographyKeyStoreTypes
import KMSKeystoreOperations
import KMS = Com.Amazonaws.Kms

function ExtractKmsOpaque(
error: KMSKeystoreOperations.KmsError
): (opaqueError?: Option<KMS.Types.OpaqueError>)
ensures
&& error.ComAmazonawsKms?
&& error.ComAmazonawsKms.Opaque?
==> opaqueError?.Some? && opaqueError?.value == error.ComAmazonawsKms
{
match error {
case Opaque(obj) => None
case KeyManagementException(s) => None
case ComAmazonawsKms(comAmazonawsKms: KMS.Types.Error) =>
match comAmazonawsKms {
case Opaque(obj) => Some(comAmazonawsKms)
case _ => None
}
}
}

function ExtractMessageFromKmsError(
error: KMSKeystoreOperations.KmsError
): (errorMessage?: Option<string>)
{
match error {
case Opaque(obj) => None
case KeyManagementException(s) => Some(s)
case ComAmazonawsKms(comAmazonawsKms: KMS.Types.Error) =>
match comAmazonawsKms {
case Opaque(obj) => None
case _ => comAmazonawsKms.message
}
}
}

function DefaultKmsErrorMessage(
nameonly localOperation: string,
nameonly kmsOperation: string,
nameonly identifier: string,
nameonly kmsArn: string,
nameonly while?: Option<string> := None,
nameonly errorMessage?: Option<string> := None
): (message: string)
{
"KMS through an exception for "
+ localOperation + "'s " + kmsOperation
+ (if while?.Some? then " while " + while?.value else ".")
+ " KMS ARN: " + kmsArn
+ "\tBranch Key ID: " + identifier
+ "\nKMS Message: " + errorMessage?.UnwrapOr("")
}

function VerifyActiveException(
branchKeyItem: KeyStoreTypes.EncryptedHierarchicalKey,
error: KMSKeystoreOperations.KmsError
): (output: Types.Error)
requires branchKeyItem.Type.ActiveHierarchicalSymmetricVersion?
{
var message := DefaultKmsErrorMessage(
localOperation := "InitializeMutation",
kmsOperation := "ReEncrypt",
identifier := branchKeyItem.Identifier,
kmsArn := branchKeyItem.KmsArn,
while? := Some("verifying the Active Branch Key. Do you have permission for the original KMS ARN?"),
errorMessage? := ExtractMessageFromKmsError(error));
Types.MutationFromException(message := message)
}

function VerifyTerminalException(
branchKeyItem: KeyStoreTypes.EncryptedHierarchicalKey,
error: KMSKeystoreOperations.KmsError
): (output: Types.Error)
requires branchKeyItem.Type.HierarchicalSymmetricVersion?
{
var message := DefaultKmsErrorMessage(
localOperation := "ApplyMutation",
kmsOperation := "ReEncrypt",
identifier := branchKeyItem.Identifier,
kmsArn := branchKeyItem.KmsArn,
while? := Some("verifying a Version with terminal properities."
+ " Do you have permission for the terminal KMS ARN?"
+ " Version (Decrypt Only): " + branchKeyItem.Type.HierarchicalSymmetricVersion.Version),
errorMessage? := ExtractMessageFromKmsError(error));
Types.MutationToException(message := message)
}

// https://github.com/smithy-lang/smithy-dafny/issues/609
// TODO-Mutations-GA :: Once we can get a string from KMS Oapque,
// we can check it for ReEncryptTo or ReEncryptFrom.
// Than, this function can return
// MutationToException or MutationFromException
function MutateException(
branchKeyItem: KeyStoreTypes.EncryptedHierarchicalKey,
error: KMSKeystoreOperations.KmsError,
terminalKmsArn: string
): (output: Types.Error)
requires branchKeyItem.Type.HierarchicalSymmetricVersion? || branchKeyItem.Type.ActiveHierarchicalSymmetricBeacon?
{
var while? :=
if branchKeyItem.Type.HierarchicalSymmetricVersion?
then Some("mutating a Version."
+ " Do you have permission for the original and terminal KMS ARN?"
+ " Version (Decrypt Only): " + branchKeyItem.Type.HierarchicalSymmetricVersion.Version)
else Some("mutating the Beacon Key."
+ " Do you have permission for the the original and terminal KMS ARN?");
// https://github.com/smithy-lang/smithy-dafny/issues/609
// If opaqueKmsError?.Some?, parse for ReEncryptTo or ReEncrytFrom
var opaqueKmsError? := ExtractKmsOpaque(error);
var message := DefaultKmsErrorMessage(
localOperation := "ApplyMutation",
kmsOperation := "ReEncrypt",
identifier := branchKeyItem.Identifier,
kmsArn := "original: " + branchKeyItem.KmsArn + "\tterminal: " + terminalKmsArn,
while? := while?,
errorMessage? := ExtractMessageFromKmsError(error));
if opaqueKmsError?.Some?
then Types.ComAmazonawsKms(ComAmazonawsKms := opaqueKmsError?.value)
else Types.KeyStoreAdminException(message := message)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ include "../Model/AwsCryptographyKeyStoreAdminTypes.dfy"
include "MutationStateStructures.dfy"
include "PrefixUtils.dfy"
include "MutationValidation.dfy"
include "MutationErrorRefinement.dfy"

module {:options "/functionSyntax:4" } Mutations {
import opened StandardLibrary
Expand All @@ -29,6 +30,7 @@ module {:options "/functionSyntax:4" } Mutations {

import PrefixUtils
import MutationValidation
import MutationErrorRefinement

const DEFAULT_APPLY_PAGE_SIZE := 3 as StandardLibrary.UInt.int32

Expand Down Expand Up @@ -258,21 +260,24 @@ module {:options "/functionSyntax:4" } Mutations {
assert MutationToApply.Terminal.customEncryptionContext.Keys !! Structure.BRANCH_KEY_RESTRICTED_FIELD_NAMES;
// -= BEGIN Version Active Branch Key
// --= Validate Active Branch Key
// TODO-Mutations-GA? :: If the KMS Call fails with access denied,
// it indicates that the MPL Consumer does not have access to the
// the original key.
:- VerifyEncryptedHierarchicalKey(
var verifyActive? := VerifyEncryptedHierarchicalKey(
item := activeItem,
keyManagerStrategy := keyManagerStrategy
);
if (verifyActive?.Fail?) {
return Failure(
MutationErrorRefinement.VerifyActiveException(
branchKeyItem := activeItem,
error := verifyActive?.error));
}

// -= Assert Beacon Key is in Original
:- Need(
readItems.BeaconItem.KmsArn == MutationToApply.Original.kmsArn,
Types.UnexpectedStateException(
message :=
"Beacon Item is not encrypted with the same KMS Key as ACTIVE!"
+ " For Initialize Mutation to succeed, the ACTIVE & Beacon Key MUST be in the same, original state."
+ " For Initialize Mutation to succeed, the ACTIVE & Beacon Key MUST have the same KMS-ARN and Custom Encryption Context!"
));
:- Need(
readItems.BeaconItem.EncryptionContext
Expand Down Expand Up @@ -530,15 +535,19 @@ module {:options "/functionSyntax:4" } Mutations {
var item := itemsToProcess[versionIndex];
match item {
case itemTerminal(item) =>
// TODO-Mutations-GA? :: If the KMS Call fails with access denied,
// the agent has lost access to the terminal Key.
:- VerifyEncryptedHierarchicalKey(
var verify? := VerifyEncryptedHierarchicalKey(
item := item,
keyManagerStrategy:= keyManagerStrategy
);
if (verify?.Fail?) {
return Failure(
MutationErrorRefinement.VerifyTerminalException(
branchKeyItem := item,
error := verify?.error));
}
logStatements := logStatements
+ [Types.MutatedBranchKeyItem(
ItemType := "Decrypt Only: " + item.Type.HierarchicalSymmetricVersion.Version,
ItemType := "Version (Decrypt Only): " + item.Type.HierarchicalSymmetricVersion.Version,
Description := " Validated in Terminal")];
// if item is original, mutate with Failure
case itemOriginal(item) =>
Expand Down Expand Up @@ -626,7 +635,7 @@ module {:options "/functionSyntax:4" } Mutations {
nameonly item: Types.AwsCryptographyKeyStoreTypes.EncryptedHierarchicalKey,
nameonly keyManagerStrategy: keyManagerStrat
)
returns (output: Outcome<Types.Error>)
returns (output: Outcome<KMSKeystoreOperations.KmsError>)
requires keyManagerStrategy.reEncrypt?

requires Structure.EncryptedHierarchicalKey?(item)
Expand All @@ -650,7 +659,7 @@ module {:options "/functionSyntax:4" } Mutations {
output := if throwAway?.Success? then
Pass
else
Fail(Types.Error.AwsCryptographyKeyStore(throwAway?.error));
Fail(throwAway?.error);
}

method {:isolate_assertions} ReEncryptHierarchicalKey(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import software.amazon.cryptography.keystore.internaldafny.types.EncryptedHierarchicalKey;
import software.amazon.cryptography.keystore.internaldafny.types.Error;
import software.amazon.cryptography.keystore.internaldafny.types.Error_AlreadyExistsConditionFailed;
import software.amazon.cryptography.keystore.internaldafny.types.Error_KeyManagementException;
import software.amazon.cryptography.keystore.internaldafny.types.Error_KeyStorageException;
import software.amazon.cryptography.keystore.internaldafny.types.Error_KeyStoreException;
import software.amazon.cryptography.keystore.internaldafny.types.Error_MutationLockConditionFailed;
Expand Down Expand Up @@ -82,6 +83,7 @@
import software.amazon.cryptography.keystore.internaldafny.types.WriteNewEncryptedBranchKeyVersionOutput;
import software.amazon.cryptography.keystore.model.AlreadyExistsConditionFailed;
import software.amazon.cryptography.keystore.model.CollectionOfErrors;
import software.amazon.cryptography.keystore.model.KeyManagementException;
import software.amazon.cryptography.keystore.model.KeyStorageException;
import software.amazon.cryptography.keystore.model.KeyStoreException;
import software.amazon.cryptography.keystore.model.MutationLockConditionFailed;
Expand All @@ -98,6 +100,9 @@ public static Error Error(RuntimeException nativeValue) {
if (nativeValue instanceof AlreadyExistsConditionFailed) {
return ToDafny.Error((AlreadyExistsConditionFailed) nativeValue);
}
if (nativeValue instanceof KeyManagementException) {
return ToDafny.Error((KeyManagementException) nativeValue);
}
if (nativeValue instanceof KeyStorageException) {
return ToDafny.Error((KeyStorageException) nativeValue);
}
Expand Down Expand Up @@ -991,6 +996,15 @@ public static Error Error(AlreadyExistsConditionFailed nativeValue) {
return new Error_AlreadyExistsConditionFailed(message);
}

public static Error Error(KeyManagementException nativeValue) {
DafnySequence<? extends Character> message;
message =
software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence(
nativeValue.message()
);
return new Error_KeyManagementException(message);
}

public static Error Error(KeyStorageException nativeValue) {
DafnySequence<? extends Character> message;
message =
Expand Down
Loading

0 comments on commit 8f17d17

Please sign in to comment.