Skip to content

Commit

Permalink
[C#] Dynamodb updates and feature requests (#220)
Browse files Browse the repository at this point in the history
* Add support for global dynamodb tables
* Add support for EndpointConfiguration, TableName and RegionSuffix
* Add more test cases
* Added integration test to verify backwards compatibility
* Remove BOM characters
* Minor change in serviceURL
* Update Directory.Build.props
  • Loading branch information
nikoo28 authored Jun 10, 2020
1 parent b9b27b5 commit 51ef58d
Show file tree
Hide file tree
Showing 21 changed files with 518 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
</ItemGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="../AppEncryption/AppEncryption.csproj" />
<ProjectReference Include="../AppEncryption.Tests/AppEncryption.Tests.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="config.yaml" CopyToOutputDirectory="Always" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public IEnumerator<object[]> GetEnumerator()

private object[] GenerateMocks(KeyState cacheIK, KeyState metaIK, KeyState cacheSK, KeyState metaSK)
{
Partition partition = new Partition(
Partition partition = new DefaultPartition(
cacheIK + "CacheIK_" + metaIK + "MetaIK_" + DateTimeUtils.GetCurrentTimeAsUtcIsoDateTimeOffset() +
"_" + Random.Next(),
cacheSK + "CacheSK_" + metaSK + "MetaSK_" + DateTimeUtils.GetCurrentTimeAsUtcIsoDateTimeOffset() + "_" + Random.Next(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Text;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using GoDaddy.Asherah.AppEncryption.Kms;
using GoDaddy.Asherah.AppEncryption.Persistence;
using GoDaddy.Asherah.AppEncryption.Tests;
using GoDaddy.Asherah.AppEncryption.Tests.AppEncryption.Persistence;
using GoDaddy.Asherah.Crypto;
using Xunit;

namespace GoDaddy.Asherah.AppEncryption.IntegrationTests.Regression
{
public class DynamoDbCompatibilityTest : IClassFixture<DynamoDBContainerFixture>, IClassFixture<MetricsFixture>
{
private const string StaticMasterKey = "thisIsAStaticMasterKeyForTesting";
private const string PartitionKey = "Id";
private const string SortKey = "Created";
private const string DefaultTableName = "EncryptionKey";

private readonly DynamoDbMetastoreImpl dynamoDbMetastoreImpl;
private readonly DynamoDbMetastoreImpl dynamoDbMetastoreImplWithKeySuffix;

public DynamoDbCompatibilityTest(DynamoDBContainerFixture dynamoDbContainerFixture)
{
// Use AWS SDK to create client and initialize table
AmazonDynamoDBConfig amazonDynamoDbConfig = new AmazonDynamoDBConfig
{
ServiceURL = dynamoDbContainerFixture.ServiceUrl,
AuthenticationRegion = "us-west-2",
};
IAmazonDynamoDB tempDynamoDbClient = new AmazonDynamoDBClient(amazonDynamoDbConfig);
CreateTableRequest request = new CreateTableRequest
{
TableName = DefaultTableName,
AttributeDefinitions = new List<AttributeDefinition>
{
new AttributeDefinition(PartitionKey, ScalarAttributeType.S),
new AttributeDefinition(SortKey, ScalarAttributeType.N),
},
KeySchema = new List<KeySchemaElement>
{
new KeySchemaElement(PartitionKey, KeyType.HASH),
new KeySchemaElement(SortKey, KeyType.RANGE),
},
ProvisionedThroughput = new ProvisionedThroughput(1L, 1L),
};
tempDynamoDbClient.CreateTableAsync(request).Wait();

// Use a builder without the suffix
dynamoDbMetastoreImpl = DynamoDbMetastoreImpl.NewBuilder()
.WithEndPointConfiguration(dynamoDbContainerFixture.ServiceUrl, "us-west-2")
.Build();

// Connect to the same metastore but initialize it with a key suffix
dynamoDbMetastoreImplWithKeySuffix = DynamoDbMetastoreImpl.NewBuilder()
.WithEndPointConfiguration(dynamoDbContainerFixture.ServiceUrl, "us-west-2")
.WithKeySuffix("us-west-2")
.Build();
}

[Fact]
private void TestRegionSuffixBackwardCompatibility()
{
string dataRowString;
string originalPayloadString;
string decryptedPayloadString;

// Encrypt originalPayloadString with metastore without key suffix
using (SessionFactory sessionFactory = SessionFactory.NewBuilder("productId", "reference_app")
.WithMetastore(dynamoDbMetastoreImpl)
.WithCryptoPolicy(new NeverExpiredCryptoPolicy())
.WithKeyManagementService(new StaticKeyManagementServiceImpl(StaticMasterKey))
.Build())
{
using (Session<byte[], byte[]> sessionBytes = sessionFactory.GetSessionBytes("shopper123"))
{
originalPayloadString = "mysupersecretpayload";
byte[] dataRowRecordBytes =
sessionBytes.Encrypt(Encoding.UTF8.GetBytes(originalPayloadString));

// Consider this us "persisting" the DRR
dataRowString = Convert.ToBase64String(dataRowRecordBytes);
}
}

// Decrypt dataRowString with metastore with key suffix
using (SessionFactory sessionFactory = SessionFactory.NewBuilder("productId", "reference_app")
.WithMetastore(dynamoDbMetastoreImplWithKeySuffix)
.WithCryptoPolicy(new NeverExpiredCryptoPolicy())
.WithKeyManagementService(new StaticKeyManagementServiceImpl(StaticMasterKey))
.Build())
{
using (Session<byte[], byte[]> sessionBytes = sessionFactory.GetSessionBytes("shopper123"))
{
byte[] newDataRowRecordBytes = Convert.FromBase64String(dataRowString);

// Decrypt the payload
decryptedPayloadString = Encoding.UTF8.GetString(sessionBytes.Decrypt(newDataRowRecordBytes));
}
}

// Verify that we were able to decrypt with a suffixed builder
Assert.Equal(decryptedPayloadString, originalPayloadString);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
namespace GoDaddy.Asherah.AppEncryption.Tests.AppEncryption
{
[Collection("Logger Fixture collection")]
public class PartitionTest
public class DefaultPartitionTest
{
private const string TestPartitionId = "test_partition_id";
private const string TestServiceId = "test_service_id";
private const string TestProductId = "test_product_id";

private readonly Partition partition;

public PartitionTest()
public DefaultPartitionTest()
{
partition = new Partition(TestPartitionId, TestServiceId, TestProductId);
partition = new DefaultPartition(TestPartitionId, TestServiceId, TestProductId);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace GoDaddy.Asherah.AppEncryption.Tests.AppEncryption.Envelope
public class EnvelopeEncryptionJsonImplTest : IClassFixture<MetricsFixture>
{
private readonly Partition partition =
new Partition("shopper_123", "payments", "ecomm");
new DefaultPartition("shopper_123", "payments", "ecomm");

// Setup DateTimeOffsets truncated to seconds and separated by hour to isolate overlap in case of interacting with multiple level keys
private readonly DateTimeOffset drkDateTime = DateTimeOffset.UtcNow.Truncate(TimeSpan.FromSeconds(1));
Expand Down Expand Up @@ -188,7 +188,7 @@ private void TestWithIntermediateKeyForReadWithKeyCachedAndNotExpiredShouldUseCa
byte[] actualBytes = envelopeEncryptionJsonImplSpy.Object.WithIntermediateKeyForRead(
keyMetaMock.Object, functionWithIntermediateKey);
Assert.Equal(expectedBytes, actualBytes);
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(It.IsAny<DateTimeOffset>()), Times.Never);
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(It.IsAny<KeyMeta>()), Times.Never);
intermediateCryptoKeyMock.Verify(x => x.Dispose());
}

Expand All @@ -206,7 +206,7 @@ private void TestWithIntermediateKeyForReadWithKeyCachedAndNotExpiredAndNotifyEx
byte[] actualBytes = envelopeEncryptionJsonImplSpy.Object.WithIntermediateKeyForRead(
keyMetaMock.Object, functionWithIntermediateKey);
Assert.Equal(expectedBytes, actualBytes);
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(It.IsAny<DateTimeOffset>()), Times.Never);
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(It.IsAny<KeyMeta>()), Times.Never);

// TODO : Add verify for notification not being called once implemented
intermediateCryptoKeyMock.Verify(x => x.Dispose());
Expand All @@ -228,7 +228,7 @@ private void TestWithIntermediateKeyForReadWithKeyCachedAndExpiredAndNotifyExpir
byte[] actualBytes = envelopeEncryptionJsonImplSpy.Object.WithIntermediateKeyForRead(
keyMetaMock.Object, functionWithIntermediateKey);
Assert.Equal(expectedBytes, actualBytes);
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(It.IsAny<DateTimeOffset>()), Times.Never);
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(It.IsAny<KeyMeta>()), Times.Never);

// TODO : Add verify for notification not being called once implemented
intermediateCryptoKeyMock.Verify(x => x.Dispose());
Expand All @@ -238,7 +238,7 @@ private void TestWithIntermediateKeyForReadWithKeyCachedAndExpiredAndNotifyExpir
private void TestWithIntermediateKeyForReadWithKeyNotCachedAndCannotCacheAndNotExpiredShouldLookup()
{
keyMetaMock.Setup(x => x.Created).Returns(ikDateTime);
envelopeEncryptionJsonImplSpy.Setup(x => x.GetIntermediateKey(It.IsAny<DateTimeOffset>()))
envelopeEncryptionJsonImplSpy.Setup(x => x.GetIntermediateKey(It.IsAny<KeyMeta>()))
.Returns(intermediateCryptoKeyMock.Object);

byte[] expectedBytes = { 0, 1, 2, 3 };
Expand All @@ -247,7 +247,7 @@ private void TestWithIntermediateKeyForReadWithKeyNotCachedAndCannotCacheAndNotE
byte[] actualBytes = envelopeEncryptionJsonImplSpy.Object.WithIntermediateKeyForRead(
keyMetaMock.Object, functionWithIntermediateKey);
Assert.Equal(expectedBytes, actualBytes);
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(keyMetaMock.Object.Created));
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(keyMetaMock.Object));
intermediateKeyCacheMock.Verify(x => x.PutAndGetUsable(It.IsAny<DateTimeOffset>(), It.IsAny<CryptoKey>()), Times.Never);
intermediateCryptoKeyMock.Verify(x => x.Dispose());
}
Expand All @@ -256,7 +256,7 @@ private void TestWithIntermediateKeyForReadWithKeyNotCachedAndCannotCacheAndNotE
private void TestWithIntermediateKeyForReadWithKeyNotCachedAndCanCacheAndNotExpiredShouldLookupAndCache()
{
keyMetaMock.Setup(x => x.Created).Returns(ikDateTime);
envelopeEncryptionJsonImplSpy.Setup(x => x.GetIntermediateKey(ikDateTime))
envelopeEncryptionJsonImplSpy.Setup(x => x.GetIntermediateKey(keyMetaMock.Object))
.Returns(intermediateCryptoKeyMock.Object);
cryptoPolicyMock.Setup(x => x.CanCacheIntermediateKeys()).Returns(true);
intermediateCryptoKeyMock.Setup(x => x.GetCreated()).Returns(ikDateTime);
Expand All @@ -270,7 +270,7 @@ private void TestWithIntermediateKeyForReadWithKeyNotCachedAndCanCacheAndNotExpi
byte[] actualBytes = envelopeEncryptionJsonImplSpy.Object.WithIntermediateKeyForRead(
keyMetaMock.Object, functionWithIntermediateKey);
Assert.Equal(expectedBytes, actualBytes);
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(keyMetaMock.Object.Created));
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(keyMetaMock.Object));
intermediateKeyCacheMock.Verify(x => x.PutAndGetUsable(intermediateCryptoKeyMock.Object.GetCreated(), intermediateCryptoKeyMock.Object));
intermediateCryptoKeyMock.Verify(x => x.Dispose());
}
Expand All @@ -279,7 +279,7 @@ private void TestWithIntermediateKeyForReadWithKeyNotCachedAndCanCacheAndNotExpi
private void TestWithIntermediateKeyForReadWithKeyNotCachedAndCanCacheAndCacheUpdateFailsShouldLookupAndFailAndDisposeKey()
{
keyMetaMock.Setup(x => x.Created).Returns(ikDateTime);
envelopeEncryptionJsonImplSpy.Setup(x => x.GetIntermediateKey(It.IsAny<DateTimeOffset>()))
envelopeEncryptionJsonImplSpy.Setup(x => x.GetIntermediateKey(It.IsAny<KeyMeta>()))
.Returns(intermediateCryptoKeyMock.Object);
cryptoPolicyMock.Setup(x => x.CanCacheIntermediateKeys()).Returns(true);
intermediateKeyCacheMock.Setup(x => x.PutAndGetUsable(It.IsAny<DateTimeOffset>(), It.IsAny<CryptoKey>()))
Expand All @@ -290,7 +290,7 @@ private void TestWithIntermediateKeyForReadWithKeyNotCachedAndCanCacheAndCacheUp

Assert.Throws<AppEncryptionException>(() => envelopeEncryptionJsonImplSpy.Object.WithIntermediateKeyForRead(
keyMetaMock.Object, functionWithIntermediateKey));
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(keyMetaMock.Object.Created));
envelopeEncryptionJsonImplSpy.Verify(x => x.GetIntermediateKey(keyMetaMock.Object));
intermediateCryptoKeyMock.Verify(x => x.Dispose());
}

Expand Down Expand Up @@ -1262,7 +1262,8 @@ private void TestGetIntermediateKeyWithParentKeyMetaShouldSucceed()
envelopeEncryptionJsonImplSpy.Setup(x => x.DecryptKey(keyRecord, systemCryptoKeyMock.Object))
.Returns(intermediateCryptoKeyMock.Object);

CryptoKey actualIntermediateKey = envelopeEncryptionJsonImplSpy.Object.GetIntermediateKey(ikDateTime);
keyMetaMock.Setup(x => x.Created).Returns(ikDateTime);
CryptoKey actualIntermediateKey = envelopeEncryptionJsonImplSpy.Object.GetIntermediateKey(keyMetaMock.Object);
Assert.Equal(intermediateCryptoKeyMock.Object, actualIntermediateKey);
envelopeEncryptionJsonImplSpy.Verify(x => x.WithExistingSystemKey(
(KeyMeta)keyRecord.ParentKeyMeta, false, It.IsAny<Func<CryptoKey, CryptoKey>>()));
Expand All @@ -1279,7 +1280,7 @@ private void TestGetIntermediateKeyWithoutParentKeyMetaShouldFail()
envelopeEncryptionJsonImplSpy.Setup(x => x.LoadKeyRecord(It.IsAny<string>(), ikDateTime))
.Returns(keyRecord);

Assert.Throws<MetadataMissingException>(() => envelopeEncryptionJsonImplSpy.Object.GetIntermediateKey(ikDateTime));
Assert.Throws<MetadataMissingException>(() => envelopeEncryptionJsonImplSpy.Object.GetIntermediateKey(keyMetaMock.Object));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class AppJsonEncryptionImplTest : IClassFixture<MetricsFixture>

public AppJsonEncryptionImplTest()
{
partition = new Partition("PARTITION", "SYSTEM", "PRODUCT");
partition = new DefaultPartition("PARTITION", "SYSTEM", "PRODUCT");
Dictionary<string, JObject> memoryPersistence = new Dictionary<string, JObject>();

dataPersistence = new AdhocPersistence<JObject>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,5 +339,11 @@ private void TestDbConnectionClosedAfterStore()
// Verify that DbConnection is closed at the end of the function call
Assert.Equal(ConnectionState.Closed, dbConnection.State);
}

[Fact]
private void TestKeySuffixShouldReturnEmpty()
{
Assert.Equal(string.Empty, adoMetastoreImplSpy.Object.GetKeySuffix());
}
}
}
Loading

0 comments on commit 51ef58d

Please sign in to comment.