Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:awslabs/aws-encryption-sdk-dafny…
Browse files Browse the repository at this point in the history
… into multi-keyring-children-as-sequence

# Conflicts:
#	src/extern/dotnet/RSAEncryption.cs
#	test/api/dotnet/ClientTests.cs
  • Loading branch information
robin-aws committed Mar 9, 2020
2 parents 228a289 + 283869f commit c5886e4
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 58 deletions.
2 changes: 1 addition & 1 deletion src/AWSEncryptionSDK.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

Expand Down
19 changes: 19 additions & 0 deletions src/api/dotnet/DafnyFFI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@ public static Option<T> NullableToOption<T>(T t)
public enum RSAPaddingModes {
PKCS1, OAEP_SHA1, OAEP_SHA256, OAEP_SHA384, OAEP_SHA512
}

public static RSAEncryption.PaddingMode RSAPaddingModesToDafnyPaddingMode(RSAPaddingModes paddingModes)
{
switch (paddingModes) {
case DafnyFFI.RSAPaddingModes.PKCS1:
return RSAEncryption.PaddingMode.create_PKCS1();
case DafnyFFI.RSAPaddingModes.OAEP_SHA1:
return RSAEncryption.PaddingMode.create_OAEP__SHA1();
case DafnyFFI.RSAPaddingModes.OAEP_SHA256:
return RSAEncryption.PaddingMode.create_OAEP__SHA256();
case DafnyFFI.RSAPaddingModes.OAEP_SHA384:
return RSAEncryption.PaddingMode.create_OAEP__SHA384();
case DafnyFFI.RSAPaddingModes.OAEP_SHA512:
return RSAEncryption.PaddingMode.create_OAEP__SHA512();
default:
throw new ArgumentException("Unsupported RSA Padding Mode");
};
}

public enum AESWrappingAlgorithm {
AES_GCM_128, AES_GCM_192, AES_GCM_256
}
Expand Down
27 changes: 14 additions & 13 deletions src/api/dotnet/Keyrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,19 @@ public static RawAESKeyring MakeRawAESKeyring(byte[] nameSpace, byte[] name, byt
{
// TODO: Check for null values
RawAESKeyring result = new RawAESKeyring();
EncryptionSuites.EncryptionSuite wrappingAlgDafny = wrappingAlgorithm switch {
DafnyFFI.AESWrappingAlgorithm.AES_GCM_128 => EncryptionSuites.__default.AES__GCM__128,
DafnyFFI.AESWrappingAlgorithm.AES_GCM_192 => EncryptionSuites.__default.AES__GCM__192,
DafnyFFI.AESWrappingAlgorithm.AES_GCM_256 => EncryptionSuites.__default.AES__GCM__256,
_ => throw new ArgumentException("Unsupported AES Wrapping Algorithm")
EncryptionSuites.EncryptionSuite wrappingAlgDafny;
switch (wrappingAlgorithm) {
case DafnyFFI.AESWrappingAlgorithm.AES_GCM_128:
wrappingAlgDafny = EncryptionSuites.__default.AES__GCM__128;
break;
case DafnyFFI.AESWrappingAlgorithm.AES_GCM_192:
wrappingAlgDafny = EncryptionSuites.__default.AES__GCM__192;
break;
case DafnyFFI.AESWrappingAlgorithm.AES_GCM_256:
wrappingAlgDafny = EncryptionSuites.__default.AES__GCM__256;
break;
default:
throw new ArgumentException("Unsupported AES Wrapping Algorithm");
};
result.__ctor(
DafnyFFI.SequenceFromByteArray(nameSpace),
Expand All @@ -60,14 +68,7 @@ public static RawRSAKeyring MakeRawRSAKeyring(byte[] nameSpace, byte[] name, Daf
{
// TODO: check for null values, ensure at least one key is non-null.
RawRSAKeyring result = new RawRSAKeyring();
RSAEncryption.PaddingMode paddingModeDafny = paddingMode switch {
DafnyFFI.RSAPaddingModes.PKCS1 => RSAEncryption.PaddingMode.create_PKCS1(),
DafnyFFI.RSAPaddingModes.OAEP_SHA1 => RSAEncryption.PaddingMode.create_OAEP__SHA1(),
DafnyFFI.RSAPaddingModes.OAEP_SHA256 => RSAEncryption.PaddingMode.create_OAEP__SHA256(),
DafnyFFI.RSAPaddingModes.OAEP_SHA384 => RSAEncryption.PaddingMode.create_OAEP__SHA384(),
DafnyFFI.RSAPaddingModes.OAEP_SHA512 => RSAEncryption.PaddingMode.create_OAEP__SHA512(),
_ => throw new ArgumentException("Unsupported RSA Padding Mode")
};
RSAEncryption.PaddingMode paddingModeDafny = DafnyFFI.RSAPaddingModesToDafnyPaddingMode(paddingMode);
RSAEncryption.PublicKey publicKeyWrapper = null;
RSAEncryption.PrivateKey privateKeyWrapper = null;
if (publicKey != null) {
Expand Down
8 changes: 6 additions & 2 deletions src/extern/dotnet/RSAEncryption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,19 @@ private static AsymmetricKeyParameter GetPublicKeyFromByteSeq(ibyteseq key) {
}
}

public static void GenerateKeyPairExtern(int strength, PaddingMode padding, out ibyteseq publicKey, out ibyteseq privateKey) {
public static void GenerateKeyPairBytes(int strength, PaddingMode padding, out byte[] publicKeyBytes, out byte[] privateKeyBytes) {
RsaKeyPairGenerator keygen = new RsaKeyPairGenerator();
SecureRandom secureRandom = new SecureRandom();
keygen.Init(new RsaKeyGenerationParameters(
BigInteger.ValueOf(RSA_PUBLIC_EXPONENT), secureRandom, strength, RSA_CERTAINTY));
AsymmetricCipherKeyPair keygenPair = keygen.GenerateKeyPair();
GetPemBytes(keygenPair, out publicKeyBytes, out privateKeyBytes);
}

public static void GenerateKeyPairExtern(int strength, PaddingMode padding, out ibyteseq publicKey, out ibyteseq privateKey) {
byte[] publicKeyBytes;
byte[] privateKeyBytes;
GetPemBytes(keygenPair, out publicKeyBytes, out privateKeyBytes);
GenerateKeyPairBytes(strength, padding, out publicKeyBytes, out privateKeyBytes);
publicKey = byteseq.FromArray(publicKeyBytes);
privateKey = byteseq.FromArray(privateKeyBytes);
}
Expand Down
2 changes: 1 addition & 1 deletion src/extern/dotnet/Signature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public static ibyteseq SerializePublicKey(ECPublicKeyParameters keyParams) {
if (xBytes.Length < fieldByteSize) {
var paddingLength = fieldByteSize - xBytes.Length;
var paddedX = new byte[fieldByteSize];
System.Array.Fill(paddedX, (byte)0, 0, paddingLength);
System.Array.Clear(paddedX, 0, paddingLength);
xBytes.CopyTo(paddedX, paddingLength);
xBytes = paddedX;
}
Expand Down
237 changes: 196 additions & 41 deletions test/api/dotnet/ClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,84 +1,239 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using KeyringDefs;
using KMSUtils;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Security;
using Xunit;

using charseq = Dafny.Sequence<char>;


namespace AWSEncryptionSDKTests
{
public class ClientTests
{

[Fact]
public void RoundTripHappyPath()
private static string SUCCESS = "SUCCESS";

// MakeKMSKeyring is a helper method that creates a KMS Keyring for unit testing
private Keyring MakeKMSKeyring()
{
String keyArn = DafnyFFI.StringFromDafnyString(TestUtils.__default.SHARED__TEST__KEY__ARN);

ClientSupplier clientSupplier = new DefaultClientSupplier();

var keyring = AWSEncryptionSDK.Keyrings.MakeKMSKeyring(
return AWSEncryptionSDK.Keyrings.MakeKMSKeyring(
clientSupplier, Enumerable.Empty<String>(), keyArn,Enumerable.Empty<String>());
}

CMMDefs.CMM cmm = AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring);
// MakeDefaultCMMWithKMSKeyring is a helper method that creates a default CMM using a KMS Keyring for unit testing
private CMMDefs.CMM MakeDefaultCMMWithKMSKeyring()
{
Keyring keyring = MakeKMSKeyring();
return AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring);
}

String plaintext = "Hello";
MemoryStream plaintextStream = new MemoryStream(Encoding.UTF8.GetBytes(plaintext));
// MakeRSAKeyring is a helper method that creates a RSA Keyring for unit testing
private Keyring MakeRSAKeyring(DafnyFFI.RSAPaddingModes paddingMode)
{
// MakeRawRSAKeyring expects DafnyFFI.RSAPaddingModes while GenerateKeyPairBytes expects
// RSAEncryption.PaddingMode
RSAEncryption.PaddingMode paddingModeDafny = DafnyFFI.RSAPaddingModesToDafnyPaddingMode(paddingMode);
byte[] publicKey;
byte[] privateKey;
RSAEncryption.RSA.GenerateKeyPairBytes(2048, paddingModeDafny, out publicKey, out privateKey);

return AWSEncryptionSDK.Keyrings.MakeRawRSAKeyring(
Encoding.UTF8.GetBytes("namespace"),
Encoding.UTF8.GetBytes("myKeyring"),
paddingMode,
publicKey,
privateKey);
}

MemoryStream ciphertext = AWSEncryptionSDK.Client.Encrypt(plaintextStream, cmm);
// MakeDefaultCMMWithRSAKeyring is a helper method that creates a default CMM using a RSA Keyring for unit testing
private CMMDefs.CMM MakeDefaultCMMWithRSAKeyring(DafnyFFI.RSAPaddingModes paddingMode)
{
Keyring keyring = MakeRSAKeyring(paddingMode);
return AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring);
}

// MakeAESKeyring is a helper method that creates an AES Keyring for unit testing
private Keyring MakeAESKeyring(DafnyFFI.AESWrappingAlgorithm wrappingAlgorithm)
{
// For our unit tests, we can just generate an AES 256 key
var keygen = GeneratorUtilities.GetKeyGenerator("AES256");
var wrappingKey = keygen.GenerateKey();

return AWSEncryptionSDK.Keyrings.MakeRawAESKeyring(
Encoding.UTF8.GetBytes("namespace"),
Encoding.UTF8.GetBytes("myKeyring"),
wrappingKey,
wrappingAlgorithm);
}

// MakeDefaultCMMWithAESKeyring is a helper method that creates a default CMM using an AES Keyring for unit testing
private CMMDefs.CMM MakeDefaultCMMWithAESKeyring(DafnyFFI.AESWrappingAlgorithm wrappingAlgorithm)
{
Keyring keyring = MakeAESKeyring(wrappingAlgorithm);
return AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring);
}

// MakeDefaultCMMWithMultiKeyring is a helper method that creates a default CMM using a Multi-Keyring for unit testing
private CMMDefs.CMM MakeDefaultCMMWithMultiKeyring()
{
Keyring generator = MakeKMSKeyring();
Keyring[] children = new Keyring[] {
MakeRSAKeyring(DafnyFFI.RSAPaddingModes.PKCS1),
MakeRSAKeyring(DafnyFFI.RSAPaddingModes.OAEP_SHA1),
MakeRSAKeyring(DafnyFFI.RSAPaddingModes.OAEP_SHA256),
MakeRSAKeyring(DafnyFFI.RSAPaddingModes.OAEP_SHA384),
MakeRSAKeyring(DafnyFFI.RSAPaddingModes.OAEP_SHA512),
MakeAESKeyring(DafnyFFI.AESWrappingAlgorithm.AES_GCM_128),
MakeAESKeyring(DafnyFFI.AESWrappingAlgorithm.AES_GCM_192),
MakeAESKeyring(DafnyFFI.AESWrappingAlgorithm.AES_GCM_256)
};

Keyring keyring = AWSEncryptionSDK.Keyrings.MakeMultiKeyring(generator, children);
return AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring);
}

// EncryptDecrypt is a helper method that performs an encrypt and then a decrypt on a plaintext that is
// formatted using a given id. withParams dictates whether Encrypt should use any additional encryption parameters
private string EncryptDecrypt(CMMDefs.CMM cmm, int id, bool withParams)
{
var plaintext = String.Format("Hello from id {0}", id);
MemoryStream plaintextStream = new MemoryStream(Encoding.UTF8.GetBytes(plaintext));
MemoryStream ciphertext = withParams
? AWSEncryptionSDK.Client.Encrypt(plaintextStream, cmm, new Dictionary<string, string>(), 0x0346, 2048)
: AWSEncryptionSDK.Client.Encrypt(plaintextStream, cmm);
MemoryStream decodedStream = AWSEncryptionSDK.Client.Decrypt(ciphertext, cmm);
StreamReader reader = new StreamReader(decodedStream, Encoding.UTF8);
String decoded = reader.ReadToEnd();
return (plaintext == decoded) ? SUCCESS : String.Format("Id: {0} failed, decoded: {1}", id, decoded);
}

Assert.Equal(plaintext, decoded);
// EncryptDecryptThreaded is a helper method that calls EncryptDecrypt in a threaded manner using either 1 thread
// if multithreading is disabled or 4 * the number of processors on the machine if it is enabled
private void EncryptDecryptThreaded(CMMDefs.CMM cmm, bool isMultithreaded, bool withParams)
{
var concurrentBag = new ConcurrentBag<String>();
var totalIds = isMultithreaded ? Environment.ProcessorCount * 4 : 1;
// Sanity check that the total number of ids is valid
Assert.True(isMultithreaded ? totalIds >= 4 : totalIds == 1);

Parallel.For(
0, totalIds, new ParallelOptions { MaxDegreeOfParallelism = totalIds },
id => { concurrentBag.Add(EncryptDecrypt(cmm, id, withParams)); }
);

var totalDecoded = 0;
foreach (string decoded in concurrentBag) {
Assert.Equal(SUCCESS, decoded);
totalDecoded += 1;
}
Assert.Equal(totalIds, totalDecoded);
}

[Fact]
public void RoundTripHappyPathWithParams()
// DefaultClientTestData represents simple client test data that does not require additional parameters outside of
// whether the test should be multithreaded and whether it should use additional params for encrypt
public static TheoryData<bool, bool> DefaultClientTestData
{
String keyArn = DafnyFFI.StringFromDafnyString(TestUtils.__default.SHARED__TEST__KEY__ARN);
get
{
var data = new TheoryData<bool, bool>();
var multithreadedList = new bool[] { true, false };
var withParamsList = new bool[] { true, false };
foreach (bool isMultithreaded in multithreadedList) {
foreach (bool withParams in withParamsList) {
data.Add(isMultithreaded, withParams);
}
}
return data;
}
}

ClientSupplier clientSupplier = new DefaultClientSupplier();
[Theory]
[MemberData(nameof(DefaultClientTestData))]
public void RoundTripHappyPath_KMS(bool isMultithreaded, bool withParams)
{
CMMDefs.CMM cmm = MakeDefaultCMMWithKMSKeyring();
EncryptDecryptThreaded(cmm, isMultithreaded, withParams);
}

var keyring = AWSEncryptionSDK.Keyrings.MakeKMSKeyring(
clientSupplier, Enumerable.Empty<String>(), keyArn,Enumerable.Empty<String>());
// RSAClientTestData represents client test data that can be used for simple RSA client tests that check all
// combinations of RSAPaddingModes and DefaultClientTestData
public static TheoryData<DafnyFFI.RSAPaddingModes, bool, bool> RSAClientTestData
{
get
{
var data = new TheoryData<DafnyFFI.RSAPaddingModes, bool, bool>();
foreach (DafnyFFI.RSAPaddingModes paddingMode in Enum.GetValues(typeof(DafnyFFI.RSAPaddingModes))) {
foreach (var item in DefaultClientTestData) {
// Since this is just being used for unit tests, and we know DefaultClientTestData is
// TheoryData<bool, bool>, cast object to bool directly
data.Add(paddingMode, (bool) item[0], (bool) item[1]);
}
}
return data;
}
}

var cmm = AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring);
[Theory]
[MemberData(nameof(RSAClientTestData))]
public void RoundTripHappyPath_RSA(DafnyFFI.RSAPaddingModes paddingMode, bool isMultithreaded, bool withParams)
{
CMMDefs.CMM cmm = MakeDefaultCMMWithRSAKeyring(paddingMode);
EncryptDecryptThreaded(cmm, isMultithreaded, withParams);
}

var plaintext = "Hello";
var plaintextStream = new MemoryStream(Encoding.UTF8.GetBytes(plaintext));

var ciphertext = AWSEncryptionSDK.Client.Encrypt(plaintextStream, cmm, new Dictionary<string, string>());

var decodedStream = AWSEncryptionSDK.Client.Decrypt(ciphertext, cmm);
var reader = new StreamReader(decodedStream, Encoding.UTF8);
var decoded = reader.ReadToEnd();

Assert.Equal(plaintext, decoded);
}
// AESClientTestData represents client test data that can be used for simple AES client tests that check all
// combinations of AESWrappingAlgorithm and DefaultClientTestData
public static TheoryData<DafnyFFI.AESWrappingAlgorithm, bool, bool> AESClientTestData
{
get
{
var data = new TheoryData<DafnyFFI.AESWrappingAlgorithm, bool, bool>();
foreach (DafnyFFI.AESWrappingAlgorithm wrappingAlgorithm in Enum.GetValues(typeof(DafnyFFI.AESWrappingAlgorithm))) {
foreach (var item in DefaultClientTestData) {
// Since this is just being used for unit tests, and we know DefaultClientTestData is
// TheoryData<bool, bool>, cast object to bool directly
data.Add(wrappingAlgorithm, (bool) item[0], (bool) item[1]);
}
}
return data;
}
}

[Theory]
[MemberData(nameof(AESClientTestData))]
public void RoundTripHappyPath_AES(DafnyFFI.AESWrappingAlgorithm wrappingAlgorithm, bool isMultithreaded, bool withParams)
{
CMMDefs.CMM cmm = MakeDefaultCMMWithAESKeyring(wrappingAlgorithm);
EncryptDecryptThreaded(cmm, isMultithreaded, withParams);
}

[Theory]
[MemberData(nameof(DefaultClientTestData))]
public void RoundTripHappyPath_MultiKeyring(bool isMultithreaded, bool withParams)
{
CMMDefs.CMM cmm = MakeDefaultCMMWithMultiKeyring();
EncryptDecryptThreaded(cmm, isMultithreaded, withParams);
}

[Fact]
public void NullPlaintext()
{
var keyArn = DafnyFFI.StringFromDafnyString(TestUtils.__default.SHARED__TEST__KEY__ARN);
var keyArn = DafnyFFI.StringFromDafnyString(TestUtils.__default.SHARED__TEST__KEY__ARN);

ClientSupplier clientSupplier = new DefaultClientSupplier();
ClientSupplier clientSupplier = new DefaultClientSupplier();

var keyring = AWSEncryptionSDK.Keyrings.MakeKMSKeyring(
clientSupplier, Enumerable.Empty<String>(), keyArn,Enumerable.Empty<String>());
var keyring = AWSEncryptionSDK.Keyrings.MakeKMSKeyring(
clientSupplier, Enumerable.Empty<String>(), keyArn,Enumerable.Empty<String>());

var cmm = AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring);
var cmm = AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring);

Assert.Throws<NullReferenceException>(() =>
AWSEncryptionSDK.Client.Encrypt(null, cmm, new Dictionary<string, string>()));
Assert.Throws<NullReferenceException>(() =>
AWSEncryptionSDK.Client.Encrypt(null, cmm, new Dictionary<string, string>()));
}

// TODO-RS: Test for nulls and other Dafny requirement violations
Expand Down

0 comments on commit c5886e4

Please sign in to comment.