/// <summary> /// Decrypts a provided XML element. /// </summary> /// <param name="encryptedElement">Encrypted XML element.</param> /// <param name="ct">Cancellation token.</param> /// <returns>Decrypted XML element.</returns> #pragma warning disable S3242 // Not altering Microsoft interface definition public async Task <XElement> DecryptAsync(XElement encryptedElement, CancellationToken ct) #pragma warning restore S3242 { ValidateConfig(); logger?.LogDebug("Decrypting ciphertext DataProtection key using AWS key {0}", Config.KeyId); using (var memoryStream = new MemoryStream()) { byte[] protectedKey = Convert.FromBase64String((string)encryptedElement.Element("value")); await memoryStream.WriteAsync(protectedKey, 0, protectedKey.Length, ct); var response = await kmsClient.DecryptAsync(new DecryptRequest { EncryptionContext = ContextUpdater.GetEncryptionContext(Config, dpOptions.Value), GrantTokens = Config.GrantTokens, CiphertextBlob = memoryStream }, ct) .ConfigureAwait(false); // Help indicates that Plaintext might be empty if the key couldn't be retrieved but // testing shows that you always get an exception thrown first using (var plaintext = response.Plaintext) { // Ignoring all the good reasons mentioned in KmsXmlEncryptor and that the implementation would // be error-prone, hard to test & review, as well as vary between NET Full & NET Core, it's not // actually permitted to access the buffer of response.Plaintext because it was populated in // the SDK from a constructor which disallows any subsequent writing. // // Yet more reasons that this needs to be handled at a framework level, providing clear Secure* primitives. return(XElement.Load(plaintext)); } } }
/// <summary> /// Encrypts the provided XML element. /// </summary> /// <param name="plaintextElement">XML element to encrypt.</param> /// <param name="ct">Cancellation token.</param> /// <returns>Encrypted XML data.</returns> #pragma warning disable S3242 // Not altering Microsoft interface definition public async Task <EncryptedXmlInfo> EncryptAsync(XElement plaintextElement, CancellationToken ct) #pragma warning restore S3242 { ValidateConfig(); logger?.LogDebug("Encrypting plaintext DataProtection key using AWS key {0}", Config.KeyId); // Some implementations of this e.g. DpapiXmlEncryptor go to some lengths to create a memory // stream, use unsafe code to pin & zero it, and so on. // // Currently not doing any such zeroing here, as this neglects that the XElement above is in memory, // is a managed construct containing ultimately a System.String, and therefore the plaintext is // already at risk of compromise, copying during GC, paging to disk etc. If we'd been starting with SecureString, // there'd be a good pre-existing case for handling the subsequent memory copies carefully (and it'd // essentially be forced as you can't copy or stream a SecureString without unsafe code). // // Even ignoring that, the subsequent code sending a MemoryStream out over the web to AWS calls ToArray inside // the SDK and then stores the result as a System.String, twice, as part of outgoing JSON, and that's // before considering HTTP-layer buffering... // // Since the AWS code eventually just gets UTF8 byte[] for request content, the ideal would be that // instead of a memory stream and a standard JSON handler, the AWS code prepares all the usual JSON and then // gets a specific UTF8 byte[] entry of the base64 plaintext which a caller can pin and erase (and the SDK could // do the same with its own request content byte[]). // // It doesn't. // // Even then the HttpClient usage would buffer the plaintext enroute. // // In conclusion pinning & zeroing this particular stream seems to be complex & error-prone overkill for // handling data that is already exposed in memory - not that I wouldn't be thrilled to see // a properly reviewed SecureMemoryStream and SecureHttpStreamContent in the framework... // // To at least reduce stream allocation churn & thus copying, pre-allocate a reasonable capacity. using (var memoryStream = new MemoryStream(4096)) { plaintextElement.Save(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); var response = await kmsClient.EncryptAsync(new EncryptRequest { EncryptionContext = ContextUpdater.GetEncryptionContext(Config, dpOptions.Value), GrantTokens = Config.GrantTokens, KeyId = Config.KeyId, Plaintext = memoryStream }, ct) .ConfigureAwait(false); using (var cipherText = response.CiphertextBlob) { var element = new XElement("encryptedKey", new XComment(" This key is encrypted with AWS Key Management Service. "), new XElement("value", Convert.ToBase64String(cipherText.ToArray()))); return(new EncryptedXmlInfo(element, typeof(KmsXmlDecryptor))); } } }