// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 include "../Keyring.dfy" include "../Materials.dfy" include "../../StandardLibrary/StandardLibrary.dfy" include "../Materials.dfy" include "../../Generated/AwsCryptographicMaterialProviders.dfy" include "../../../libraries/src/Collections/Sequences/Seq.dfy" module {:extern "Dafny.Aws.EncryptionSdk.Core.MultiKeyring"} MaterialProviders.MultiKeyring { import opened StandardLibrary import opened Wrappers import Aws.Crypto import Keyring import Materials import UTF8 import Seq class MultiKeyring extends Keyring.VerifiableInterface, Crypto.IKeyring { const generatorKeyring: Option<Crypto.IKeyring> const childKeyrings: seq<Crypto.IKeyring> constructor ( generatorKeyring: Option<Crypto.IKeyring>, childKeyrings: seq<Crypto.IKeyring> ) //= compliance/framework/multi-keyring.txt#2.6 //= type=implication //# On keyring initialization, a keyring MUST define at least one of the //# following: requires generatorKeyring.Some? || |childKeyrings| > 0 //= compliance/framework/multi-keyring.txt#2.6.1 //= type=implication //# If the list of child keyrings (Section 2.6.2) is empty, a generator //# keyring (Section 2.6.1) MUST be defined for the keyring. requires |childKeyrings| == 0 ==> generatorKeyring.Some? //= compliance/framework/multi-keyring.txt#2.6.2 //= type=implication //# If this keyring does not have a generator keyring (Section 2.6.1), //# this list MUST NOT be empty. requires generatorKeyring.None? ==> |childKeyrings| > 0 ensures this.generatorKeyring == generatorKeyring ensures this.childKeyrings == childKeyrings { this.generatorKeyring := generatorKeyring; this.childKeyrings := childKeyrings; } //= compliance/framework/multi-keyring.txt#2.6.1 //= type=implication //# This keyring MUST implement the Generate Data Key (keyring- //# interface.md#generate-data-key) behavior during OnEncrypt (keyring- //# interface.md#onencrypt). method OnEncrypt(input: Crypto.OnEncryptInput) returns (res: Result<Crypto.OnEncryptOutput, Crypto.IAwsCryptographicMaterialProvidersException>) ensures res.Success? ==> && Materials.EncryptionMaterialsTransitionIsValid( input.materials, res.value.materials ) ensures res.Success? ==> //= compliance/framework/multi-keyring.txt#2.6.1 //= type=implication //# This means that this keyring MUST return //# encryption materials (structures.md#encryption-materials) containing //# a plaintext data key on OnEncrypt (keyring-interface.md#onencrypt). && res.value.materials.plaintextDataKey.Some? //= compliance/framework/multi-keyring.txt#2.7.1 //= type=implication //# If this keyring does not have a generator keyring (Section 2.6.1), //# and the input encryption materials (structures.md#encryption- //# materials) does not include a plaintext data key, OnEncrypt MUST //# fail. ensures && this.generatorKeyring.None? && input.materials.plaintextDataKey.None? ==> res.Failure? //= compliance/framework/multi-keyring.txt#2.7.1 //= type=implication //# * If the input encryption materials already include a plaintext data //# key, OnEncrypt MUST fail. ensures && this.generatorKeyring.Some? && input.materials.plaintextDataKey.Some? ==> res.Failure? { // We could also frame this as "Need", but I found an "if" statement more clearly matches the // requirement in the spec ("If this keyring does not have a generator keyring // and the input encryption materials does not include a plaintext data key") if this.generatorKeyring.None? && input.materials.plaintextDataKey.None? { var exception := new Crypto.AwsCryptographicMaterialProvidersException( "Need either a generator keyring or input encryption materials which contain a plaintext data key"); return Failure(exception); } var returnMaterials := input.materials; //= compliance/framework/multi-keyring.txt#2.7.1 //# If this keyring has a generator keyring, this keyring MUST first //# generate a plaintext data key using the generator keyring: if this.generatorKeyring.Some? { :- Crypto.Need(input.materials.plaintextDataKey.None?, "This multi keyring has a generator but provided Encryption Materials already contain plaintext data key"); //= compliance/framework/multi-keyring.txt#2.7.1 //# * This keyring MUST first call the generator keyring's OnEncrypt //# using the input encryption materials as input. var onEncryptOutput := this.generatorKeyring.value.OnEncrypt(input); //= compliance/framework/multi-keyring.txt#2.7.1 //# * If the generator keyring fails OnEncrypt, this OnEncrypt MUST also //# fail. :- Crypto.Need(onEncryptOutput.Success?, "Generator keyring failed to generate plaintext data key"); //= compliance/framework/multi-keyring.txt#2.7.1 //# * If the generator keyring returns encryption materials missing a //# plaintext data key, OnEncrypt MUST fail. :- Crypto.Need(Materials.EncryptionMaterialsTransitionIsValid(input.materials, onEncryptOutput.value.materials), "Generator keyring returned invalid encryption materials"); returnMaterials := onEncryptOutput.value.materials; } for i := 0 to |this.childKeyrings| invariant returnMaterials.plaintextDataKey.Some? { var onEncryptInput := Crypto.OnEncryptInput(materials := returnMaterials); //= compliance/framework/multi-keyring.txt#2.7.1 //# Next, for each keyring (keyring-interface.md) in this keyring's list //# of child keyrings (Section 2.6.2), the keyring MUST call OnEncrypt //# (keyring-interface.md#onencrypt). var onEncryptOutput := this.childKeyrings[i].OnEncrypt(onEncryptInput); //= compliance/framework/multi-keyring.txt#2.7.1 //# If the child keyring's OnEncrypt (keyring- //# interface.md#onencrypt) fails, this OnEncrypt MUST also fail. :- Crypto.Need(onEncryptOutput.Success?, "Child keyring failed to encrypt plaintext data key"); // We have to explicitly check for this because our child and generator keyrings are of type // IKeyring, rather than VerifiableKeyring. // If we knew we would always have VerifiableKeyrings, we would get this for free. // However, we want to support customer implementations of keyrings which may or may // not perform valid transitions. :- Crypto.Need(Materials.EncryptionMaterialsTransitionIsValid(returnMaterials, onEncryptOutput.value.materials), "Child keyring performed invalid transition on encryption materials"); returnMaterials := onEncryptOutput.value.materials; } :- Crypto.Need(Materials.EncryptionMaterialsTransitionIsValid(input.materials, returnMaterials), "A child or generator keyring modified the encryption materials in illegal ways."); //= compliance/framework/multi-keyring.txt#2.7.1 //# If all previous OnEncrypt (keyring-interface.md#onencrypt) calls //# succeeded, this keyring MUST return the encryption materials //# (structures.md#encryption-materials) returned by the last OnEncrypt //# call. return Success(Crypto.OnEncryptOutput(materials := returnMaterials)); } /* * OnDecrypt */ method OnDecrypt(input: Crypto.OnDecryptInput) returns (res: Result<Crypto.OnDecryptOutput, Crypto.IAwsCryptographicMaterialProvidersException>) ensures res.Success? ==> && Materials.DecryptionMaterialsTransitionIsValid( input.materials, res.value.materials ) //= compliance/framework/multi-keyring.txt#2.7.2 //= type=implication //# If the decryption materials already contain a plaintext data key, the //# keyring MUST fail and MUST NOT modify the decryption materials //# (structures.md#decryption-materials). // The "MUST NOT modify" clause is true because objects are immutable in Dafny. ensures Materials.DecryptionMaterialsWithPlaintextDataKey(input.materials) ==> res.Failure? { // We won't actually be returning these materials, but it's useful to have a reference to them // for proving things (for example, proving we never enter a state where we get a plaintext data // key from a child keyring and DON'T return it) var materials := input.materials; :- Crypto.Need(Materials.DecryptionMaterialsWithoutPlaintextDataKey(input.materials), "Keyring received decryption materials that already contain a plaintext data key."); // Failure messages will be collected here var failures : seq<string> := []; //= compliance/framework/multi-keyring.txt#2.7.2 //# Otherwise, OnDecrypt MUST first attempt to decrypt the encrypted data //# keys (structures.md#encrypted-data-keys-1) in the input decryption //# materials (structures.md#decryption-materials) using its generator //# keyring (Section 2.6.1). if this.generatorKeyring.Some? { var result := AttemptDecryptDataKey(this.generatorKeyring.value, input); if result.Success? { if result.value.materials.plaintextDataKey.Some? { return Success(result.value); } } else { failures := failures + [result.error]; } } //= compliance/framework/multi-keyring.txt#2.7.2 //# If the generator keyring is unable to //# decrypt the materials, the multi-keyring MUST attempt to decrypt //# using its child keyrings, until one either succeeds in decryption or //# all have failed. for j := 0 to |this.childKeyrings| invariant Materials.DecryptionMaterialsWithoutPlaintextDataKey(materials) { //= compliance/framework/multi-keyring.txt#2.7.2 //# For each keyring (keyring-interface.md) to be used for decryption, //# the multi-keyring MUST call that keyring's OnDecrypt (keyring- //# interface.md#ondecrypt) using the unmodified decryption materials //# (structures.md#decryption-materials) and the input encrypted data key //# (structures.md#encrypted-data-key) list. var result := AttemptDecryptDataKey(this.childKeyrings[j], input); if result.Success? { //= compliance/framework/multi-keyring.txt#2.7.2 //# If OnDecrypt (keyring- //# interface.md#ondecrypt) returns decryption materials //# (structures.md#decryption-materials) containing a plaintext data key, //# the multi-keyring MUST immediately return the modified decryption //# materials. // We don't explicitly check for "containing a plaintext data key" // because AttemptDecryptDataKey has a post-condition ensuring that // if the call is successful, the result has a plaintext data key return Success(result.value); } else { //= compliance/framework/multi-keyring.txt#2.7.2 //# If the child keyring's OnDecrypt call fails, the multi- //# keyring MUST collect the error and continue to the next keyring, if //# any. failures := failures + [result.error]; } } //= compliance/framework/multi-keyring.txt#2.7.2 //# If, after calling OnDecrypt (keyring-interface.md#ondecrypt) on every //# child keyring (Section 2.6.2) (and possibly the generator keyring //# (Section 2.6.1)), the decryption materials (structures.md#decryption- //# materials) still do not contain a plaintext data key, OnDecrypt MUST //# return a failure message containing the collected failure messages //# from the child keyrings. // Note that the annotation says this should only happen if there is no // plaintext data key. From our proofs above (the loop invariant of // DecryptionMaterialsWithoutPlaintextDataKey), we know that the *only* // way to get to this place is if there is no plaintext data key, so we // omit the 'if' statement checking for it. var concatString := (s, a) => a + "\n" + s; var error := Seq.FoldRight( concatString, failures, "Unable to decrypt data key:\n" ); var combinedResult := new Crypto.AwsCryptographicMaterialProvidersException(error); return Failure(combinedResult); } } method AttemptDecryptDataKey(keyring: Crypto.IKeyring, input: Crypto.OnDecryptInput) returns (res: Result<Crypto.OnDecryptOutput, string>) ensures res.Success? ==> && res.value.materials.plaintextDataKey.Some? && Materials.DecryptionMaterialsTransitionIsValid(input.materials, res.value.materials) { var decryptResult := keyring.OnDecrypt(input); if decryptResult.Failure? { return Failure(decryptResult.error.GetMessage()); } var output := decryptResult.value; :- Need( Materials.DecryptionMaterialsTransitionIsValid(input.materials, output.materials), "Keyring performed invalid material transition" ); return Success(output); } }