public async Task PutAsync(string name, string secret, string kmsKey = "alias/credstash", bool autoVersion = true, string versionArg = null, Dictionary <string, string> encryptionContext = null, int?expireEpoch = null) { var version = autoVersion ? IntWithPadding((await GetHighestVersion(name).ConfigureAwait(false) + 1).ToString()) : IntWithPadding(versionArg ?? string.Empty); var keyDataResponse = await GenerateKeyData(kmsKey, encryptionContext).ConfigureAwait(false); var encryptionResponse = SealAes(keyDataResponse, secret); var key = Convert.ToBase64String(keyDataResponse.CiphertextBlob); var contents = Convert.ToBase64String(encryptionResponse.CipherText); var hmac = encryptionResponse.Hmac.ToHexString(); var credstashItem = new CredstashItem(name, version, contents, CredstashItem.DefaultDigest, hmac, key); await _amazonDynamoDb.PutItemAsync(new PutItemRequest { Item = CredstashItem.ToAttributeValueDict(credstashItem, expireEpoch), TableName = Options.Table, ConditionExpression = "attribute_not_exists(#name)", ExpressionAttributeNames = new Dictionary <string, string> { { "#name", "name" } } }).ConfigureAwait(false); }
public async Task <Optional <string> > GetSecretAsync(string name, string version = null, Dictionary <string, string> encryptionContext = null, bool throwOnInvalidCipherTextException = true) { CredstashItem item; if (version == null) { var response = await _amazonDynamoDb.QueryAsync(new QueryRequest() { TableName = Options.Table, Limit = 1, ScanIndexForward = false, ConsistentRead = true, KeyConditions = new Dictionary <string, Condition>() { { "name", new Condition() { ComparisonOperator = ComparisonOperator.EQ, AttributeValueList = new List <AttributeValue>() { new AttributeValue(name) } } } } }).ConfigureAwait(false); item = CredstashItem.From(response.Items.FirstOrDefault()); } else { var response = await _amazonDynamoDb.GetItemAsync(new GetItemRequest() { TableName = Options.Table, Key = new Dictionary <string, AttributeValue>() { { "name", new AttributeValue(name) }, { "version", new AttributeValue(version) }, } }).ConfigureAwait(false); item = CredstashItem.From(response.Item); } if (item == null) { return(null); } DecryptResponse decryptResponse; try { decryptResponse = await _amazonKeyManagementService.DecryptAsync(new DecryptRequest() { CiphertextBlob = new MemoryStream(Convert.FromBase64String(item.Key)), EncryptionContext = encryptionContext }).ConfigureAwait(false); } catch (InvalidCiphertextException e) { if (throwOnInvalidCipherTextException) { throw new CredstashException("Could not decrypt hmac key with KMS. The credential may " + "require that an encryption context be provided to decrypt " + "it.", e); } return(new Optional <string>()); } catch (Exception e) { throw new CredstashException("Decryption error", e); } var bytes = decryptResponse.Plaintext.ToArray(); var key = new byte[32]; var hmacKey = new byte[32]; Buffer.BlockCopy(bytes, 0, key, 0, 32); Buffer.BlockCopy(bytes, 32, hmacKey, 0, 32); var contents = Convert.FromBase64String(item.Contents); var hmac = new HMACSHA256(hmacKey); var result = hmac.ComputeHash(contents); if (!result.ToHexString().Equals(item.Hmac)) { throw new CredstashException($"HMAC Failure for {item.Name} v{item.Version}"); } IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding"); cipher.Init(false, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", key), _initializationVector)); byte[] plaintext = cipher.DoFinal(contents); return(Encoding.UTF8.GetString(plaintext)); }