From 1e27fceee97c21edf2a3af3a11cfde6f1b6a0a8a Mon Sep 17 00:00:00 2001 From: Luke Maurer Date: Fri, 22 Nov 2019 15:39:13 -0800 Subject: [PATCH 01/10] Benchmarks and Java support The benchmark is largely the Main testing module, but with a much larger plaintext and with a timer. The Java packaging is a little rough; something better could probably be done with Gradle or somithing simpler than Maven. --- Makefile | 21 ++- bench.pom.xml | 86 +++++++++++ src/Bench.dfy | 107 ++++++++++++++ src/extern/dotnet/Time.cs | 23 +++ src/extern/java/AESEncryption/AES_GCM.java | 53 +++++++ src/extern/java/Arrays/Array.java | 22 +++ .../java/BouncyCastleCryptoMac/HMac.java | 75 ++++++++++ src/extern/java/RSAEncryption/RSA.java | 128 ++++++++++++++++ src/extern/java/Random/__default.java | 16 ++ src/extern/java/Signature/ECDSA.java | 138 ++++++++++++++++++ src/extern/java/Signature/__default.java | 4 + src/extern/java/Time/Timer.java | 15 ++ src/extern/java/UTF8/__default.java | 37 +++++ .../Utils/AlgorithmNotSupportedException.java | 7 + src/extern/java/Utils/BouncyCastleUtils.java | 20 +++ src/extern/java/Utils/Util.java | 61 ++++++++ 16 files changed, 810 insertions(+), 3 deletions(-) create mode 100644 bench.pom.xml create mode 100644 src/Bench.dfy create mode 100644 src/extern/dotnet/Time.cs create mode 100644 src/extern/java/AESEncryption/AES_GCM.java create mode 100644 src/extern/java/Arrays/Array.java create mode 100644 src/extern/java/BouncyCastleCryptoMac/HMac.java create mode 100644 src/extern/java/RSAEncryption/RSA.java create mode 100644 src/extern/java/Random/__default.java create mode 100644 src/extern/java/Signature/ECDSA.java create mode 100644 src/extern/java/Signature/__default.java create mode 100644 src/extern/java/Time/Timer.java create mode 100644 src/extern/java/UTF8/__default.java create mode 100644 src/extern/java/Utils/AlgorithmNotSupportedException.java create mode 100644 src/extern/java/Utils/BouncyCastleUtils.java create mode 100644 src/extern/java/Utils/Util.java diff --git a/Makefile b/Makefile index f1a0de4d7..47d0a212d 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ endif # Add to this as more files can be verified. # Eventually this can be something like: # SRCS = $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.dfy)) -SRCS = \ +LIBSRCS = \ src/Crypto/AESEncryption.dfy \ src/Crypto/EncryptionSuites.dfy \ src/Crypto/Digests.dfy \ @@ -18,7 +18,6 @@ SRCS = \ src/Crypto/Random.dfy \ src/Crypto/RSAEncryption.dfy \ src/Crypto/Signature.dfy \ - src/Main.dfy \ src/SDK/AlgorithmSuite.dfy \ src/SDK/Client.dfy \ src/SDK/CMM/DefaultCMM.dfy \ @@ -40,6 +39,12 @@ SRCS = \ src/Util/Streams.dfy \ src/Util/UTF8.dfy \ +SRCS = $(LIBSRCS) \ + src/Main.dfy + +BENCHSRCS = $(LIBSRCS) \ + src/Bench.dfy + SRCV = $(patsubst src/%.dfy, build/%.dfy.verified, $(SRCS)) DEPS_CS = $(wildcard src/extern/dotnet/*.cs) @@ -62,7 +67,17 @@ build/%.dfy.verified: src/%.dfy $(DAFNY) $(patsubst build/%.dfy.verified, src/%.dfy, $@) /compile:0 && mkdir -p $(dir $@) && touch $@ build/Main.exe: $(SRCS) $(DEPS) - $(DAFNY) /out:build/Main $(SRCS) $(DEPS) /compile:2 /noVerify /noIncludes && cp $(BCDLL) build/ + $(DAFNY) /out:build/Main $(SRCS) $(DEPS) /compile:2 /noVerify /noIncludes /spillTargetCode:1 && cp $(BCDLL) build/ + +build/Bench.exe: $(BENCHSRCS) $(DEPS) + $(DAFNY) /out:build/Bench $(BENCHSRCS) $(DEPS) /compile:2 /noVerify /noIncludes /spillTargetCode:1 && cp $(BCDLL) build/ + +build/java/src/bench/bench.java: $(BENCHSRCS) + $(DAFNY) /out:build/java/src/bench $(BENCHSRCS) /compile:0 /noVerify /noIncludes /spillTargetCode:3 /compileTarget:java + +build/java/bench.jar: $(BENCHSRCS) build/java/src/bench/bench.java + mvn -f bench.pom.xml package + cp build/java/aws-encryption-sdk-dafny-benchmarks-1.0-SNAPSHOT-jar-with-dependencies.jar build/java/bench.jar buildjs: $(SRCS) $(DAFNY) /out:build/Main $(SRCS) /compile:2 /noVerify /noIncludes /compileTarget:js /spillTargetCode:1 diff --git a/bench.pom.xml b/bench.pom.xml new file mode 100644 index 000000000..20b20035d --- /dev/null +++ b/bench.pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + com.example + aws-encryption-sdk-dafny-benchmarks + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + 1.8 + + + + + org.bouncycastle + bcprov-jdk16 + 1.46 + + + + + dafny.lang + DafnyRuntime + 1 + + + + + src/extern/java + build/java + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add-source + generate-sources + + add-source + + + + build/java/src/bench + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + + + bench + + + + + jar-with-dependencies + + + + + + + + diff --git a/src/Bench.dfy b/src/Bench.dfy new file mode 100644 index 000000000..9d8ae10f2 --- /dev/null +++ b/src/Bench.dfy @@ -0,0 +1,107 @@ +include "SDK/Keyring/RawRSAKeyring.dfy" +include "SDK/Materials.dfy" +include "StandardLibrary/StandardLibrary.dfy" +include "StandardLibrary/UInt.dfy" +include "SDK/CMM/Defs.dfy" +include "SDK/CMM/DefaultCMM.dfy" +include "SDK/Client.dfy" +include "SDK/MessageHeader.dfy" +include "Crypto/RSAEncryption.dfy" +include "Util/UTF8.dfy" +include "StandardLibrary/Base64.dfy" + +module Main { + import opened StandardLibrary + import opened UInt = StandardLibrary.UInt + import CMMDefs + import DefaultCMMDef + import RSAEncryption + import RawRSAKeyringDef + import Materials + import Client = ESDKClient + import Msg = MessageHeader + import UTF8 + import Base64 + + method Replicate(s : string, n : UInt.uint32) returns (ret : string) + requires |s| * n as int < UInt.UINT32_LIMIT + ensures |ret| == |s| * n as int && forall i :: 0 <= i < |ret| ==> ret[i] == s[i % |s|] { + if n == 0 { + return ""; + } + + var l := |s| as UInt.uint32; + var a := new char[l * n]; + var i := 0 as UInt.uint32; + + while i < l * n invariant 0 <= i <= l * n && forall j :: 0 <= j < i ==> a[j] == s[j % l] { + a[i] := s[i % l]; + i := i + 1; + } + + assert forall i :: 0 <= i < l * n ==> a[i] == s[i % l]; + + return a[..]; + } + + module {:extern "Time"} Time { + //method {:extern} CurrentTimeMillis() returns (a : nat) + class {:extern "Timer"} Timer { + constructor {:extern} () { } + method {:extern} ElapsedMilliseconds() returns (a : nat) + } + } + + method Main() { + var namespace := UTF8.Encode("namespace"); + var name := UTF8.Encode("MyKeyring"); + if name.Failure? || namespace.Failure? { + print "Failure: hardcoded name/namespace cannot be utf8 encoded"; + return; + } + + var ek, dk := RSAEncryption.RSA.RSAKeygen(2048, RSAEncryption.PKCS1); + var keyring := new RawRSAKeyringDef.RawRSAKeyring(namespace.value, name.value, RSAEncryption.RSAPaddingMode.PKCS1, 2048, Some(ek), Some(dk)); + var cmm := new DefaultCMMDef.DefaultCMM.OfKeyring(keyring); + + var n := 200 * 1024; + + var msg := Replicate("lorem ipsum dolor sit amet ", n); + var originalPlaintext := UTF8.Encode(msg).value; + print "Plaintext size: ", |originalPlaintext|, "\n"; + + var keyA, valA := UTF8.Encode("keyA").value, UTF8.Encode("valA").value; + var encryptionContext := [(keyA, valA)]; + assert Msg.ValidAAD(encryptionContext) by { + // To proving ValidAAD, we need to reveal the definition of ValidAAD: + reveal Msg.ValidAAD(); + // We also need to help the verifier with proving the AADLength is small: + calc { + Msg.AADLength(encryptionContext); + 2 + Msg.KVPairsLength(encryptionContext, 0, 1); + 2 + 2 + |keyA| + 2 + |valA|; + } + assert Msg.AADLength(encryptionContext) < UINT16_LIMIT; + } + + var timer := new Time.Timer(); + + var e := Client.Encrypt(originalPlaintext, cmm, encryptionContext); + if e.Failure? { + print "Bad encryption :( ", e.error, "\n"; + return; + } + + var d := Client.Decrypt(e.value, cmm); + if d.Failure? { + print "bad decryption: ", d.error, "\n"; + return; + } + var finalPlaintext := d.value; + + var elapsed := timer.ElapsedMilliseconds(); + + print elapsed, " ms\n"; + print "Match? ", (originalPlaintext == finalPlaintext), "\n"; + } +} diff --git a/src/extern/dotnet/Time.cs b/src/extern/dotnet/Time.cs new file mode 100644 index 000000000..10b9542e8 --- /dev/null +++ b/src/extern/dotnet/Time.cs @@ -0,0 +1,23 @@ +using System; +using System.Diagnostics; +using System.Numerics; + +namespace Time { + //public partial class __default { + // public static BigInteger CurrentTimeMillis() { + // return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + // } + //} + + public class Timer { + private readonly Stopwatch stopwatch = new Stopwatch(); + + public Timer() { + stopwatch.Start(); + } + + public BigInteger ElapsedMilliseconds() { + return stopwatch.ElapsedMilliseconds; + } + } +} \ No newline at end of file diff --git a/src/extern/java/AESEncryption/AES_GCM.java b/src/extern/java/AESEncryption/AES_GCM.java new file mode 100644 index 000000000..a25754e0a --- /dev/null +++ b/src/extern/java/AESEncryption/AES_GCM.java @@ -0,0 +1,53 @@ +package AESEncryption; + +import dafny.DafnySequence; +import dafny.UByte; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +import static Utils.Util.bytesToUByteSequence; +import static Utils.Util.uByteSequenceToBytes; + +//TODO This code has yet to be reviewed. See issue #36 +public class AES_GCM { + + public static STL.Result AESEncrypt(EncryptionSuites.EncryptionSuite encAlg, + DafnySequence iv, + DafnySequence key, + DafnySequence msg, + DafnySequence aad) { + try { + GCMBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + AEADParameters param = new AEADParameters(new KeyParameter(uByteSequenceToBytes(key)), encAlg.tagLen.intValue() * 8, uByteSequenceToBytes(iv), uByteSequenceToBytes(aad)); + cipher.init(true, param); + + byte[] c = new byte[cipher.getOutputSize(msg.length())]; + int len = cipher.processBytes(uByteSequenceToBytes(msg), 0, msg.length(), c, 0); + cipher.doFinal(c, len); //Append authentication tag to `c` + return new STL.Result_Success(__default.EncryptionOutputFromByteSeq(bytesToUByteSequence(c), encAlg)); + } + catch (InvalidCipherTextException e) { + return new STL.Result_Failure(DafnySequence.asString("aes encrypt err")); + } + } + + public static STL.Result> AESDecrypt(EncryptionSuites.EncryptionSuite encAlg, DafnySequence key, DafnySequence cipherText, DafnySequence authTag, DafnySequence iv, DafnySequence aad) { + try { + GCMBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + AEADParameters param = new AEADParameters(new KeyParameter(uByteSequenceToBytes(key)), encAlg.tagLen.intValue() * 8, uByteSequenceToBytes(iv), uByteSequenceToBytes(aad)); + cipher.init(false, param); + DafnySequence ctx = cipherText.concatenate(authTag); + byte[] pt = new byte[cipher.getOutputSize(ctx.length())]; + int len = cipher.processBytes(uByteSequenceToBytes(ctx), 0, ctx.length(), pt, 0); + cipher.doFinal(pt, len); //Check message authentication tag + return new STL.Result_Success>(bytesToUByteSequence(pt)); + } catch (InvalidCipherTextException macEx) { + return new STL.Result_Failure>(DafnySequence.asString(macEx.toString())); + } catch (Exception e) { + return new STL.Result_Failure>(DafnySequence.asString("aes decrypt err")); + } + } +} diff --git a/src/extern/java/Arrays/Array.java b/src/extern/java/Arrays/Array.java new file mode 100644 index 000000000..c090f1627 --- /dev/null +++ b/src/extern/java/Arrays/Array.java @@ -0,0 +1,22 @@ +package Arrays; + +import java.math.BigInteger; + +public class Array { + private Array() { } + + public static T[] copy(String td, T[] source, BigInteger length) { + if (length.intValue() == source.length) { + return source.clone(); + } else { + @SuppressWarnings("unchecked") + T[] dest = (T[]) java.lang.reflect.Array.newInstance( + source.getClass().getComponentType(), length.intValue()); + System.arraycopy(source, 0, dest, 0, length.intValue()); + return dest; + } + } + public static void copyTo(T[] source, T[] dest, BigInteger offset) { + System.arraycopy(source, 0, dest, offset.intValue(), source.length); + } +} diff --git a/src/extern/java/BouncyCastleCryptoMac/HMac.java b/src/extern/java/BouncyCastleCryptoMac/HMac.java new file mode 100644 index 000000000..c4d50cb23 --- /dev/null +++ b/src/extern/java/BouncyCastleCryptoMac/HMac.java @@ -0,0 +1,75 @@ +package BouncyCastleCryptoMac; + +import BouncyCastleCryptoMac.CipherParameters; +import Digests.HMAC_ALGORITHM; +import Utils.AlgorithmNotSupportedException; +import Utils.Util; +import dafny.DafnySequence; +import dafny.UByte; + +import java.math.BigInteger; + +public class HMac extends _ExternBase_HMac { + + private org.bouncycastle.crypto.macs.HMac bcHMac; + + public HMac(Digests.HMAC_ALGORITHM algorithm) { + org.bouncycastle.crypto.Digest digest; + if(algorithm.is_HmacSHA256()) { + digest = new org.bouncycastle.crypto.digests.SHA256Digest(); + bcHMac = new org.bouncycastle.crypto.macs.HMac(digest); + } else if(algorithm.is_HmacSHA384()) { + digest = new org.bouncycastle.crypto.digests.SHA384Digest(); + bcHMac = new org.bouncycastle.crypto.macs.HMac(digest); + } else { + throw new AlgorithmNotSupportedException(algorithm.toString() + " not supported."); + } + } + + @Override + public DafnySequence getAlgorithmName() { + return DafnySequence.asString(bcHMac.getAlgorithmName()); + } + + @Override + public BigInteger getMacSize() { + return BigInteger.valueOf(bcHMac.getMacSize()); + } + + @Override + public void init(CipherParameters ps) { + if(ps.is_KeyParameter()) { + org.bouncycastle.crypto.params.KeyParameter bcKeyParameter = new org.bouncycastle.crypto.params.KeyParameter(Util.uBytesToBytes(ps.key)); + bcHMac.init(bcKeyParameter); + } + } + + @Override + public void reset() { + bcHMac.reset(); + } + + @Override + public void updateSingle(UByte input) { + bcHMac.update(input.byteValue()); + } + + @Override + public void update(UByte[] input , BigInteger inOff, BigInteger len) { + bcHMac.update(Util.uBytesToBytes(input), Util.bigIntegerToInt(inOff), Util.bigIntegerToInt(len)); + } + + @Override + public BigInteger doFinal(UByte[] output, BigInteger outOff) { + byte[] bytes = new byte[output.length - Util.bigIntegerToInt(outOff)]; + BigInteger ans = BigInteger.valueOf(bcHMac.doFinal(bytes, 0)); + System.arraycopy(Util.bytesToUBytes(bytes), 0, output, outOff.intValue(), bytes.length); + return ans; + } + + @Override + public HMAC_ALGORITHM getUnderlyingDigest() { + // TODO + throw new UnsupportedOperationException(); + } +} diff --git a/src/extern/java/RSAEncryption/RSA.java b/src/extern/java/RSAEncryption/RSA.java new file mode 100644 index 000000000..195a43bcd --- /dev/null +++ b/src/extern/java/RSAEncryption/RSA.java @@ -0,0 +1,128 @@ +package RSAEncryption; + +import Utils.BouncyCastleUtils; +import dafny.DafnySequence; +import dafny.Tuple2; +import dafny.UByte; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMReader; +import org.bouncycastle.openssl.PEMWriter; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.RSAKeyGenParameterSpec; + +import static Utils.Util.*; + +public class RSA { + public static Tuple2 get_pem(KeyPair kp) { + try { + byte[] pk; + { + StringWriter stringWriter = new StringWriter(); + PEMWriter pemWriter = new PEMWriter(stringWriter); + pemWriter.writeObject(kp.getPublic()); + pemWriter.flush(); + pk = stringWriter.toString().getBytes(StandardCharsets.UTF_8); + } + + byte[] sk; + { + StringWriter stringWriter = new StringWriter(); + PEMWriter pemWriter = new PEMWriter(stringWriter); + pemWriter.writeObject(kp.getPrivate()); + pemWriter.flush(); + sk = stringWriter.toString().getBytes(StandardCharsets.UTF_8); + } + + return new Tuple2<>(bytesToUBytes(pk), bytesToUBytes(sk)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static final BigInteger RSA_PUBLIC_EXPONENT = BigInteger.valueOf(65537); + // XXX This parameter is passed using the Bouncy Castle API in the C# implementation, + // but there doesn't appear to be a way to pass it using JCE + @SuppressWarnings("unused") + public static final int RSA_CERTAINTY = 256; + + public static Tuple2, DafnySequence> RSAKeygen(int bits, RSAPaddingMode padding) { + KeyPairGenerator gen; + try { + gen = KeyPairGenerator.getInstance("RSA", BouncyCastleUtils.getProvider()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + try { + gen.initialize(new RSAKeyGenParameterSpec(bits, RSA_PUBLIC_EXPONENT)); + } catch (InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } + + KeyPair kp = gen.generateKeyPair(); + Tuple2 pair = get_pem(kp); + return new Tuple2<>(DafnySequence.fromArray(pair.dtor__0()), DafnySequence.fromArray(pair.dtor__1())); + } + + public static STL.Option> RSAEncrypt(int bits, RSAPaddingMode padding, DafnySequence ek, DafnySequence msg) { + try { + PublicKey pub; + PEMReader pemReader = new PEMReader(new StringReader(uByteSequenceToString(ek))); + Object pemObject = pemReader.readObject(); + pub = ((PublicKey)pemObject); + + Cipher engine = createEngine(padding); + + engine.init(Cipher.ENCRYPT_MODE, pub); + return new STL.Option_Some<>(bytesToUByteSequence(engine.doFinal(uByteSequenceToBytes(msg)))); + } + catch (IOException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e){ + return new STL.Option_None<>(); + } + + } + + public static STL.Option> RSADecrypt(int bits, RSAPaddingMode padding, DafnySequence dk, DafnySequence ctx) { + try { + KeyPair keyPair; + + Cipher engine = createEngine(padding); + + Reader txtreader = new StringReader(uByteSequenceToString(dk)); + keyPair = (KeyPair) new PEMReader(txtreader).readObject(); + engine.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); + return new STL.Option_Some<>(bytesToUByteSequence(engine.doFinal(uByteSequenceToBytes(ctx)))); + } + catch (IOException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e){ + return new STL.Option_None<>(); + } + } + + public static Cipher createEngine(RSAPaddingMode padding) { + String alg; + + if (padding.is_PKCS1()) { + alg = "RSA/ECB/PKCS1Padding"; + } else if (padding.is_OAEP__SHA1()) { + alg = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; + } else { // padding.is_OAEP__SHA256 + alg = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; + } + + try { + return Cipher.getInstance(alg, BouncyCastleUtils.getProvider()); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/extern/java/Random/__default.java b/src/extern/java/Random/__default.java new file mode 100644 index 000000000..d1318f46e --- /dev/null +++ b/src/extern/java/Random/__default.java @@ -0,0 +1,16 @@ +package Random; + +import Utils.Util; +import dafny.DafnySequence; +import dafny.UByte; + +import java.util.Random; + +public class __default { + public static DafnySequence GenerateBytes(int i) { + Random rng = new Random(); + byte[] z = new byte[i]; + rng.nextBytes(z); + return Util.bytesToUByteSequence(z); + } +} diff --git a/src/extern/java/Signature/ECDSA.java b/src/extern/java/Signature/ECDSA.java new file mode 100644 index 000000000..194b2bf08 --- /dev/null +++ b/src/extern/java/Signature/ECDSA.java @@ -0,0 +1,138 @@ +package Signature; + +import Utils.Util; +import dafny.DafnySequence; +import dafny.Tuple2; +import dafny.UByte; +import org.bouncycastle.asn1.*; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.JDKMessageDigest; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.math.ec.ECPoint; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.SecureRandom; + +import static Utils.Util.bytesToUByteSequence; +import static Utils.Util.uByteSequenceToBytes; + +public class ECDSA { + public static STL.Option, DafnySequence>> KeyGen(ECDSAParams x) { + try { + ECKeyPairGenerator g = new ECKeyPairGenerator(); + SecureRandom rng = new SecureRandom(); + ECNamedCurveParameterSpec p; + if (x.is_ECDSA__P384()) { + p = ECNamedCurveTable.getParameterSpec("secp384r1"); + g.init(new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()), rng)); + } else { // x is ECDSA__P256 + p = ECNamedCurveTable.getParameterSpec("secp256r1"); + g.init(new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()), rng)); + } + AsymmetricCipherKeyPair kp = g.generateKeyPair(); + ECPoint pt = ((ECPublicKeyParameters)kp.getPublic()).getQ(); + DafnySequence vk = bytesToUByteSequence(pt.getEncoded()); + DafnySequence sk = bytesToUByteSequence(((ECPrivateKeyParameters)kp.getPrivate()).getD().toByteArray()); + return new STL.Option_Some<>(new Tuple2<>(vk, sk)); + } catch (RuntimeException e) { + return new STL.Option_None<>(); + } + } + + public static boolean Verify(ECDSAParams x, DafnySequence vk, DafnySequence digest, DafnySequence sig) { + try { + ECNamedCurveParameterSpec p; + if (x.is_ECDSA__P384()) { + p = ECNamedCurveTable.getParameterSpec("secp384r1"); + } else { + p = ECNamedCurveTable.getParameterSpec("secp256r1"); + } + ECDomainParameters dp = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()); + ECPoint pt = p.getCurve().decodePoint(uByteSequenceToBytes(vk)); + ECPublicKeyParameters vkp = new ECPublicKeyParameters(pt, dp); + ECDSASigner sign = new ECDSASigner(); + sign.init(false, vkp); + BigInteger r, s; + Tuple2 pair = DERDeserialize(uByteSequenceToBytes(sig)); + return sign.verifySignature(uByteSequenceToBytes(digest), pair.dtor__0(), pair.dtor__1()); + } catch (Exception e) { + return false; + } + } + + public static STL.Option> Sign(ECDSAParams x, DafnySequence sk, DafnySequence digest) { + try { + ECNamedCurveParameterSpec p; + if (x.is_ECDSA__P384()) { + p = ECNamedCurveTable.getParameterSpec("secp384r1"); + } else { + p = ECNamedCurveTable.getParameterSpec("secp256r1"); + } + ECDomainParameters dp = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()); + ECPrivateKeyParameters skp = new ECPrivateKeyParameters(new BigInteger(uByteSequenceToBytes(sk)), dp); + ECDSASigner sign = new ECDSASigner(); + sign.init(true, skp); + do { + BigInteger[] sig = sign.generateSignature(uByteSequenceToBytes(digest)); + byte[] bytes = DERSerialize(sig[0], sig[1]); + if (bytes.length != x.SignatureLength().intValue()) { + // Most of the time, a signature of the wrong length can be fixed + // by negating s in the signature relative to the group order. + bytes = DERSerialize(sig[0], p.getN().subtract(sig[1])); + } + if (bytes.length == x.SignatureLength().intValue()) { + // This will meet the method postcondition, which says that a Some? return must + // contain a sequence of bytes whose length is x.SignatureLength(). + return new STL.Option_Some<>(bytesToUByteSequence(bytes)); + } + // We only get here with low probability, so try again (forever, if we have really bad luck). + } while (true); + } catch (RuntimeException e) { + return new STL.Option_None<>(); + } + } + + public static DafnySequence Digest(ECDSAParams x, DafnySequence msg) { + MessageDigest alg; + if (x.is_ECDSA__P384()) { + alg = new JDKMessageDigest.SHA384(); + } else { + alg = new JDKMessageDigest.SHA256(); + } + byte[] digest = alg.digest(uByteSequenceToBytes(msg)); + return bytesToUByteSequence(digest); + } + + private static byte[] DERSerialize(BigInteger r, BigInteger s) { + DERSequence derSeq = new DERSequence(new ASN1Encodable[] { new DERInteger(r), new DERInteger(s) }); + try { + return derSeq.getEncoded(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static Tuple2 DERDeserialize(byte[] bytes) { + ASN1InputStream asn1 = new ASN1InputStream(bytes); + ASN1Sequence seq; + try { + seq = (ASN1Sequence) asn1.readObject(); + } catch (IOException e) { + throw new RuntimeException(e); + } + DERInteger dr = (DERInteger) seq.getObjectAt(0); + DERInteger ds = (DERInteger) seq.getObjectAt(1); + BigInteger r = new BigInteger(dr.toString()); + BigInteger s = new BigInteger(ds.toString()); + return new Tuple2<>(r, s); + } +} diff --git a/src/extern/java/Signature/__default.java b/src/extern/java/Signature/__default.java new file mode 100644 index 000000000..ab3a7ab81 --- /dev/null +++ b/src/extern/java/Signature/__default.java @@ -0,0 +1,4 @@ +package Signature; + +public class __default { +} diff --git a/src/extern/java/Time/Timer.java b/src/extern/java/Time/Timer.java new file mode 100644 index 000000000..34f0e4b5f --- /dev/null +++ b/src/extern/java/Time/Timer.java @@ -0,0 +1,15 @@ +package Time; + +import java.math.BigInteger; + +public class Timer { + private long start; + + public Timer() { + start = System.currentTimeMillis(); + } + + public BigInteger ElapsedMilliseconds() { + return BigInteger.valueOf(System.currentTimeMillis() - start); + } +} diff --git a/src/extern/java/UTF8/__default.java b/src/extern/java/UTF8/__default.java new file mode 100644 index 000000000..5e33265f3 --- /dev/null +++ b/src/extern/java/UTF8/__default.java @@ -0,0 +1,37 @@ +package UTF8; + +import Utils.Util; +import dafny.DafnySequence; +import dafny.UByte; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.*; + +public class __default extends _ExternBase___default { + public static STL.Result> Encode(DafnySequence str) { + CharsetEncoder utf8 = StandardCharsets.UTF_8.newEncoder(); + utf8.onMalformedInput(CodingErrorAction.REPORT); + utf8.onUnmappableCharacter(CodingErrorAction.REPORT); + try { + ByteBuffer utf8Buf = utf8.encode(CharBuffer.wrap(str.verbatimString())); + byte[] utf8Bytes = new byte[utf8Buf.limit()]; + utf8Buf.get(utf8Bytes); + return new STL.Result_Success<>(Util.bytesToUByteSequence(utf8Bytes)); + } catch (CharacterCodingException e) { + return new STL.Result_Failure<>(DafnySequence.asString("Input contains invalid Unicode characters")); + } + } + + public static STL.Result> Decode(DafnySequence bytes) { + CharsetDecoder utf8 = StandardCharsets.UTF_8.newDecoder(); + utf8.onMalformedInput(CodingErrorAction.REPORT); + utf8.onUnmappableCharacter(CodingErrorAction.REPORT); + try { + String decoded = utf8.decode(ByteBuffer.wrap(Util.uByteSequenceToBytes(bytes))).toString(); + return new STL.Result_Success<>(DafnySequence.asString(decoded)); + } catch (CharacterCodingException e) { + return new STL.Result_Failure<>(DafnySequence.asString("Input contains an invalid Unicode code point")); + } + } +} diff --git a/src/extern/java/Utils/AlgorithmNotSupportedException.java b/src/extern/java/Utils/AlgorithmNotSupportedException.java new file mode 100644 index 000000000..90c69313e --- /dev/null +++ b/src/extern/java/Utils/AlgorithmNotSupportedException.java @@ -0,0 +1,7 @@ +package Utils; + +public class AlgorithmNotSupportedException extends RuntimeException { + public AlgorithmNotSupportedException(String message) { + super(message); + } +} diff --git a/src/extern/java/Utils/BouncyCastleUtils.java b/src/extern/java/Utils/BouncyCastleUtils.java new file mode 100644 index 000000000..c98d471e8 --- /dev/null +++ b/src/extern/java/Utils/BouncyCastleUtils.java @@ -0,0 +1,20 @@ +package Utils; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.Security; + +public class BouncyCastleUtils { + private BouncyCastleUtils() { } + + private static final BouncyCastleProvider PROVIDER = + new BouncyCastleProvider(); + + static { + Security.addProvider(PROVIDER); + } + + public static BouncyCastleProvider getProvider() { + return PROVIDER; + } +} diff --git a/src/extern/java/Utils/Util.java b/src/extern/java/Utils/Util.java new file mode 100644 index 000000000..2bee54990 --- /dev/null +++ b/src/extern/java/Utils/Util.java @@ -0,0 +1,61 @@ +package Utils; + +import dafny.DafnySequence; +import dafny.UByte; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; + +public class Util { + private Util() { } + + public static int bigIntegerToInt(BigInteger x) { + try { + return x.intValueExact(); + } catch(ArithmeticException e) { + // TODO: error handling + System.out.println(e.toString()); + throw e; + } + } + + public static UByte[] bytesToUBytes(byte[] bytes) { + int len = bytes.length; + UByte[] ans = new UByte[len]; + for (int i = 0; i < len; i++) { + ans[i] = new UByte(bytes[i]); + } + return ans; + } + + public static byte[] uBytesToBytes(UByte[] uBytes) { + int len = uBytes.length; + byte[] ans = new byte[len]; + for (int i = 0; i < len; i++) { + ans[i] = uBytes[i].byteValue(); + } + return ans; + } + + public static DafnySequence bytesToUByteSequence(byte[] bytes) { + return DafnySequence.fromArray(bytesToUBytes(bytes)); + } + + public static byte[] uByteSequenceToBytes(DafnySequence seq) { + int len = seq.length(); + byte[] ans = new byte[len]; + int i = 0; + for (UByte b : seq) { + ans[i++] = b.byteValue(); + } + return ans; + } + + public static DafnySequence stringToUByteSequence(String string) { + return bytesToUByteSequence(string.getBytes(StandardCharsets.UTF_8)); + } + + public static String uByteSequenceToString(DafnySequence uBytes) { + return new String(uByteSequenceToBytes(uBytes), StandardCharsets.UTF_8); + } +} From 915bc7264018b9fdcfea470631d7ae1f83d0dd34 Mon Sep 17 00:00:00 2001 From: Luke Maurer Date: Mon, 16 Dec 2019 14:56:30 -0800 Subject: [PATCH 02/10] Java: Use Gradle in place of make/Maven hybrid It's still a bit rough (no way to avoid regenerating sources every time), but it works. Use `gradle jar` to build a jar or `gradle runBench` to run the benchmark. --- .gitattributes | 6 + .gitignore | 1 + Makefile | 52 ------- bench.pom.xml | 86 ----------- build.gradle | 123 +++++++++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58702 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 183 +++++++++++++++++++++++ gradlew.bat | 100 +++++++++++++ settings.gradle | 1 + {src => test/SDK}/Bench.dfy | 0 11 files changed, 419 insertions(+), 138 deletions(-) create mode 100644 .gitattributes delete mode 100644 Makefile delete mode 100644 bench.pom.xml create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle rename {src => test/SDK}/Bench.dfy (100%) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..00a51aff5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore index cdadaad74..9dadefe43 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ test/**/Output/* /test/bin /test/obj /generated-test +.gradle/* \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 14da2a2b1..000000000 --- a/Makefile +++ /dev/null @@ -1,52 +0,0 @@ -ifeq ($(OS),Windows_NT) - DAFNY = Dafny.exe -else - DAFNY = dafny -endif - -# TODO: Temporary subset of files to build and verify. -# Add to this as more files can be verified. -# Eventually this can be something like: -# SRCS = $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.dfy)) -LIBSRCS = \ - src/Crypto/AESEncryption.dfy \ - src/Crypto/EncryptionSuites.dfy \ - src/Crypto/Digests.dfy \ - src/Crypto/HKDF/CryptoMac.dfy \ - src/Crypto/HKDF/HKDF.dfy \ - src/Crypto/HKDF/HKDFSpec.dfy \ - src/Crypto/Random.dfy \ - src/Crypto/RSAEncryption.dfy \ - src/Crypto/Signature.dfy \ - src/SDK/AlgorithmSuite.dfy \ - src/SDK/Client.dfy \ - src/SDK/CMM/DefaultCMM.dfy \ - src/SDK/CMM/Defs.dfy \ - src/SDK/Deserialize.dfy \ - src/SDK/Keyring/RawAESKeyring.dfy \ - src/SDK/Keyring/Defs.dfy \ - src/SDK/Keyring/MultiKeyring.dfy \ - src/SDK/Keyring/RawRSAKeyring.dfy \ - src/SDK/Materials.dfy \ - src/SDK/MessageBody.dfy \ - src/SDK/MessageHeader.dfy \ - src/SDK/Serialize.dfy \ - src/SDK/ToyClient.dfy \ - src/StandardLibrary/Base64.dfy \ - src/StandardLibrary/StandardLibrary.dfy \ - src/StandardLibrary/UInt.dfy \ - src/Util/Arrays.dfy \ - src/Util/Streams.dfy \ - src/Util/UTF8.dfy \ - -BENCHSRCS = $(LIBSRCS) \ - src/Bench.dfy - - -build/java/src/bench/bench.java: $(BENCHSRCS) - $(DAFNY) /out:build/java/src/bench $(BENCHSRCS) /compile:0 /noVerify /noIncludes /spillTargetCode:3 /compileTarget:java - -build/java/bench.jar: $(BENCHSRCS) build/java/src/bench/bench.java - mvn -f bench.pom.xml package - cp build/java/aws-encryption-sdk-dafny-benchmarks-1.0-SNAPSHOT-jar-with-dependencies.jar build/java/bench.jar - diff --git a/bench.pom.xml b/bench.pom.xml deleted file mode 100644 index 20b20035d..000000000 --- a/bench.pom.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - 4.0.0 - - com.example - aws-encryption-sdk-dafny-benchmarks - 1.0-SNAPSHOT - - - UTF-8 - 1.8 - 1.8 - - - - - org.bouncycastle - bcprov-jdk16 - 1.46 - - - - - dafny.lang - DafnyRuntime - 1 - - - - - src/extern/java - build/java - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.0.0 - - - add-source - generate-sources - - add-source - - - - build/java/src/bench - - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - package - - single - - - - - - bench - - - - - jar-with-dependencies - - - - - - - - diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..5edb38f7d --- /dev/null +++ b/build.gradle @@ -0,0 +1,123 @@ +// TODO: This build awkwardly generates all Java sources twice, once for the +// library and once for the benchmark suite, where just one .dfy file differs +// between the two runs. It works, but there's probably a better way. + +plugins { + id 'java' + id 'maven-publish' +} + +repositories { + mavenLocal() + mavenCentral() +} + +group = 'com.example' +version = '1.0-SNAPSHOT' +sourceCompatibility = '1.8' + +project.buildDir = 'build/java' + +sourceSets { + main { + java { + srcDirs = ['src/extern/java', 'build/java/src/main'] + } + } + bench { + java { + srcDirs = ['src/extern/java', 'build/java/src/bench'] + } + } +} + +dependencies { + implementation 'org.bouncycastle:bcprov-jdk16:1.46' + implementation 'dafny.lang:DafnyRuntime:1' + + benchImplementation 'org.bouncycastle:bcprov-jdk16:1.46' + benchImplementation 'dafny.lang:DafnyRuntime:1' +} + + +def libSrcs = [ + 'src/Crypto/AESEncryption.dfy', + 'src/Crypto/EncryptionSuites.dfy', + 'src/Crypto/Digests.dfy', + 'src/Crypto/HKDF/CryptoMac.dfy', + 'src/Crypto/HKDF/HKDF.dfy', + 'src/Crypto/HKDF/HKDFSpec.dfy', + 'src/Crypto/Random.dfy', + 'src/Crypto/RSAEncryption.dfy', + 'src/Crypto/Signature.dfy', + 'src/SDK/AlgorithmSuite.dfy', + 'src/SDK/Client.dfy', + 'src/SDK/CMM/DefaultCMM.dfy', + 'src/SDK/CMM/Defs.dfy', + 'src/SDK/Deserialize.dfy', + 'src/SDK/Keyring/RawAESKeyring.dfy', + 'src/SDK/Keyring/Defs.dfy', + 'src/SDK/Keyring/MultiKeyring.dfy', + 'src/SDK/Keyring/RawRSAKeyring.dfy', + 'src/SDK/Materials.dfy', + 'src/SDK/MessageBody.dfy', + 'src/SDK/MessageHeader.dfy', + 'src/SDK/Serialize.dfy', + 'src/SDK/ToyClient.dfy', + 'src/StandardLibrary/Base64.dfy', + 'src/StandardLibrary/StandardLibrary.dfy', + 'src/StandardLibrary/UInt.dfy', + 'src/Util/Arrays.dfy', + 'src/Util/Streams.dfy', + 'src/Util/UTF8.dfy', +] + +def dafnyOpts = [ + '/compile:0', + '/noVerify', + '/noIncludes', + '/spillTargetCode:3', + '/compileTarget:java', +] + +// TODO: This should be using a custom task type so that (a) there's less +// duplication and (b) we can let Gradle know what the output directory is +// so that we can avoid regenerating sources. + +task genSources(type: Exec) { + executable = 'dafny' + args "/out:$buildDir/src/main" + args libSrcs + args 'test/SDK/Client.dfy' + args dafnyOpts +} + +task genBenchSources(type: Exec) { + executable = 'dafny' + args "/out:$buildDir/src/bench" + args libSrcs + args 'test/SDK/Bench.dfy' + args dafnyOpts +} + +gradle.projectsEvaluated { + compileJava.dependsOn genSources + compileBenchJava.dependsOn genBenchSources +} + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +task bench(type: JavaExec) { + classpath = sourceSets.bench.runtimeClasspath + main = 'bench' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b GIT binary patch literal 58702 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

@up^y4&I8N>~wvL%f(S2W%NN zf&x46sN${5Gh+I9cd>g-O|x3@x#@hdvU54zx*WtnC#5%quWk43w{;_G!4&;N;wy-O z?urjbDnKfp2u4gknf&*wBJS`YfdzBa#pf^Lo9ei}Z)MCk6MP}h0OYrd8`jVipqsRTq}lh>h#|o4yiA zbPQLKXatZ+L=I$?XEGfd7x*_lf|=3xKLi)yj}jQ9pD+OPrv;Mqe+~uywe$sD4D}uV z4@_J6*&E>)?K_L=^f9)ZpbIb0tyI>qF^OuZ;8LrA_T9JRowWUXNjyBVFxj7 zcFv)I!ZI!9%3&ro1=#}qZ!W@`!*%Do@xlC)>lS-KJPYY3@3mXj^ZUgyXXo8DiZ)0M z@ORv8NQ5xIiv%yy7WuvM3l7ZnaX8M-u4s`LZ2-*e2V%BIin4U@4b=3ps|#~L^v#DXv3GDk8H#;lK%qAV<%I5Z8dd3-sIMfqq2WY52;$Y7| zC@8Z_G%EJ3tOhCq_Ad3l4=IN9=Ee$7k#R%^@JPd7SnqL~*a3EWdfPj^Ft)B}bgnkr zBT1I)!g2ha@JU#wQW1op@1SkuaGVJcEJVhstebVvoHV+n`EI?;^p~M~tfk#K1CBi- zF<+3FQvDXkoVE)E6Bj9T)Vlo9rjgCj>S}EH&DnJgn49L@7ZaI=v&F?OY*>NLOQ-u43cR-0P{LGZCyKsW{^hNC8iDiqJ{~) zNqU!S?7Gb=jXSc_T>xTosLbq!#)VKVs^hKlReb|!_v(O0B(=A8tA0Fic+K)>Lc!(J zge-eb*cuWjJCE_q)D}kLQ`X73XAD=didg`EDAk|uw*rjJ1Yj*bj<;`v&pOnps=(g<^CaeJRd*q!NQ`O zTAcA*KCphxtD>M<0l)OpWo@|W=Vs)XFpM7C;96VQR+W3~AXoqC9@yN@7J9kuboR-H zHL8|U?V*D#Jg&`hR95a1#ByH}mfw|kcIP#b2%C}r_nxhIoWdo%k*DB;N)%#~P458H zR&1-?mh?}HxGi(-dh@nkK_H45IB{y)%qwup^p85vZeUpqh|G;9wr%q$_*4*|PS(bw z3$<2M;y;*(WAtHSM--PRyA1<)1Xe^(yuRRaZX9nR0oP5%Wg)P(ak|_q$^7Cd)NP#f zFt*;;hP)je2EkvO_Juc*@6Fd}(xbH@+`c?h1(9yjJzcLY^!{hs3;2?q^IfrF`+D{7 zeAjrrb~tUbxms|met4=I%jCVN6O3DEeY8_%NiNb1EvTu>AI1J!n@36jd$2##c}B>0 z4L;|^v$`6=K#^tk;MTA+ji{smQT)gaODj-((|WI%X2JbpJ46#0RZ&FMJeh+Z<&>04 z)cI;7Dm)CZ1Q9H0Ge@zDXKAsB9dZbg4?1joh3}_)K2k;c^(s6)kl-$}hLll_T0$(y z-4SgpruNv#}%R(l@3!%tj5l!d~Np>{BXo}gF5QWAP7*n?JW-N~>|I~-Sokci&_Ho87f;meu+(2@Yz45X{^W92m`3_^%9FadE5^cGO72ffn`$&G} zGOIPIF?FsLh^0eater8)<@~LjNIyP(W7F~ackhd7ase+Gfo@-RBG6$Q+CeDbE-eiO! z66k;0^Ze3P9kEj(yiZ!_vx)K5>+Jrl2af_iKMbiG*Z6y})9{?`w@LyvBpEEC99HEm z94J&4%248p>c%Nb+Y?Mm9%w8P;5(?F8nINf&_*-><^LeQ6{hj_UPeUhLmtxd+Vmgt zX+WF*G|x;d1!gF0D5?$*b6|tDV#m<_?(f{b+Jd?J92?)y8t>gZ+-KQ+Bj*PJW__xR zdf03Su)GBsi{L~F7m?zTiiu`Wk!YO=QO{H#)PP2?loJ6bfRs0oKxO3+aYm9`#}5V$ z`x646$5C08JvW-c>mV&jy+a+V^zH9IQ#Inj?BmB?I0~jhx7qLD!cSQ9{<) zCB(xvh>|7z&?P1A6fTeZ=vH4`HaRJenyQMrBMl$uNuOX#!uWTr0YsU$pvq9H4wY>t zl^X-E=|ppy073iT6Xv?zU&~*SOz)S{s$uTKR(W@_aAsUm!9UD9D`~`uK!3`Buc{%2B4{J%ioRlMx&#kB{e!Avb zJrlj#<)~p=4r6CfO9_3Cn1xhg=x7nk+LY}yn%fvBEBY;q4p`CSxj7WfX^CU5+@tJWJi(W&KcO*jj5x;xDLZ*AxFvIAYA@P8yW`o)9#pos(U zSgS*I-N9vd=^11lccI*yNQxzMgJ!_I?64MNHZL9-U_DIfm>8g{k^fj)WeFHM8I_z& zZ3l@3<|n0jQSo~R0*Qcqvf~?+vNohOl*bzy=)XeN;2a3p1~0V$$gAWoVuI=*iPkyO z;E~luur&+0{@(mshrT+g9pcf!^T48w$vch$Nigsv6ylw&q=E-ICa#nDgi$8vmBC($ z=yLuLM0U-^2^S`{_ZwTz$|kB|ZzUr`AM@J;{X1nZJEj`$4skl+fss?6#-GZt`JdU# zvVUW}%8!tF0rBe>`+r}#|FsnVkBs^MUX+ze>dHSpWnWVCqdl~T@Zci3NHq%q1q0&Z zjiRz*rIA75MSd&j>=Hq=uts|mK)cc}S884FYT9`Ym2Gbq-?zNU&7M-!u<)j1^s21K z7oJaB$L#M;cjw#E-oI~{yJTr2o((;6binRCTJm*%J0nrPf%?1jgigQI5bI~2dsFN451~NyCYYvfVfu5!YwE`!Uv%`& zB-2spw{|p}vcNP<;@k3}sV|3_r|H|Z4JC9~&KtI*)@JhM?U=mg#m3PjRVoE+M zVYM5uWSO==K5bE81EEz2?F$jdRB^ec45FWK&Dz+e}E=Op=h#{z^;qey2Dx+2Q2qzwA-MpAB% z6U&685w0+}tjouEmcVXOF$U)7w=8u*B7piVzASTr-X|xfrQR1uvc@IZr$CD4MUVF| zMre!R*v|cBT}rB>9#r~c4@(}lBCp$9)X`O$7f_9s)8|{>$Da!Go_qr=;4rtnr7TgXUpffMV9akHEvEw*Z&g!2Env6(!b;)$Zkq!j9UGy>Zopi zUQ<$5Ex<;BxM?&1+E#8>B$er2c?TqH!q^=LX)1lV=@=!xtMbm`$gt70@|} z8AM$V_n1o@=*E15EncO@{DFc)hEBSA@Nbk=GkNsF#}_mBtmF20k$-)eOP+G`q*EAP^>>5d@ea zg6^gb37{ol+=uYC3->5=jbqd}&J|19Oh}yYviQ}E@&>94`r85c>mo=XKA{q~2C*8q z1(8IqD#!fuWdW8DT^RfX)ssdyOzHq^sC=mmY``qcE8^g-o852h1`FBL)_0fHqqzW%Y(brO+X5H!1sl*7|2>*^XZQ^Um1qp- zj{+=uY~SxwTj1)2rmt7luK=kSptJDqqF#W3sech+R{=RBs5U1mcd@_EU~~8?dsmUjsf7tKBg%yZYVwFEDFu zWWQwnb~$%v)IaYXT;h~afPZz{4^@br zn($GS68Obz0BZLqKb0MyvEEp-F z%XZOu9nt29ll>hIY!o7Ulpi znv6Q&d-;x1Q#smNV37IAjmqJ`f>4;j)zs}@5Ggb8NHQ&r9}YcFk1=s0qSmfDIT zL}IzQfY+Hb7z3YWw>3^;vPtIw+@lL;+6f0j=R`K1?Rs$3&Ft1)@NM5zV1L&`Vbl&7 zswRx&Edg?U7fqYMBpWQ6jO&vI*KI5odc0(9&B?LUS$lNhs$&T-QLab-p|8suK`a9N zU;>Q)dneC-M2!FT|4RScQqNRUcScY|-Hb2FWK7ixX)w*zIKVgM!)R>CsoYSb9@Lsy zLJk9)H;@1=N~KM;fxCA80PT1w>bSwB_El6JKa7XzdPVs_qfTy_HegHLC>RgUxX-lj zs_$O^k~(_!_WADl_zRBtc0-mj? zs$_XlVRk8UA;TzI%p`NZo^_F0EiGU(u~@&bF!!jgly!a1es#9LBez7Usio}j;#J*M zYwchj{qF*wFL`?T^AP-=5n(>kT+$T_0iGHp4PM3Z+@Rs&k(ghDz;|7e>IBW%Q&>Q* z*|!8m`k0#8(2SfZzjS1JdAS)iL*a3Q>Tt-uHB0^>6;1Ac&)lXvA#A+^~TF&^<-Px{Arzw?$8;b z6(xcC)ary#!{#M(-LV!}WvwJ94Y}p+dl+)^9$xeZPD9+g#b-y4E)=6{dZvMSy(4bs zQqd@m1o^6YxMp0{hxGGmxj9Cv;|d+QcXE|*vQbI!0Pil2SOuAXlwDZl!rN-01kujv z`f06S5M~gsjn6G_ql(Z9v;Hz>hvm)t+G*Reo}Oz2DoZC~IJYFxV3=*1bcDI#V-ehb z`yS4?O;M_uUKUWRm9-0*%jA%+L}L(ouJ)NW*6>k4H0cLNq(fNgHv4Jnoecj0zTR!} zd#20Z0rVivt#5;(=aRdjZc}W37m&` zO8hf+O$5W$AK*8A8`$z*=vRHy=*QmoFlAg=(s#RhNTHVYC1}1K@hC|GVLZ=F6-*0x z{+sO$vPen^=y*Dt6A!PzJ!}(6LIqT()R5jys9m(YH-ka(Nn?~~Rtl-H*pP{zU-MQ? zlXus*&2qLymA^@KO>Y@ZjhbR)e1(|kVQ~2STn}zH$Hv*3wWt5KBjg$eN#@{G$fcMS8-`5K^IA7m_aM6 z`$)$n`bVh3x<&!)d?X1WLQ9uG9!?;qPGiS*BaH;RE}RifZm9eNEHWtim)l0DD^SyZww8iac z7r6e^#bzT+IQYWSF&Kq!LAalh*r_;Wzi*>jtu~LuXq%d^sr49_?y34lr!u2w+EXxL ztvGKYoa^y*IC%Ypz%YnJV8{reNW^fpBHc9m`O*l>0iqm+au0Ze=X^~VrnQF?&PU+5 zvDnPzI3)KOpigkw6k+Ys(1~ggta{l}hmoJQoMZf-VJ+IOf#vtk(!25;+d@FGwm{aR zAx2bT?D_&PU}I*Rt}$?_UtrnE;npz+3Wm#cQDminaPZX-ZsD&rZgNMlOP>~lPs)5- z1VY9g@uu8tU)@>Vy33Lo9Nkp)j+fdu6g^!Frwn87+^Rz~KEqIZNvGPU)wR*jLB$B}I$TO*f~!7t4654oLO6t8V2r?1+T_Q&0K0 z4682u*_{u6j(?P@{;`Y5=-T~Y%Kr<77Z}0&gZ+aQ{5EN9gm5}+3o-ZC$|VI0^CJnl zlu@4piaXoYaQOv8RMg_I3w0k1bN&6lEJ=n~1W@$^LZ*+5?6;J{!0RU%BNqm{<~-t- zYBiVcsKMtWrxI-wsbMy>B;oLhCnBi?O$~EZ4$9!UcL&30S4}6G<>y$P0t(I%#Lna} zX_$_w@IIB}3veH9GP|^0P;_>@eR7vav@g)kd8j3{^_~v_K#JRObGNy!PKV z%zyngxUd z^s@D@xs>D?9|0^XQSe9+5fMBr9-1rL2ipylxZmKI{+KWoVU3B__h9-y+tCNq0iyqW8C?N<_=wTWv36hc-;u6_5$-8<-iG^wVX{rs#%*o<0 zP`zZD%9FKz8kA)Pi`QrR2c(!`3^|x4*s*D2BB*E3p1pCB6wSJ(K~r=?GY2zKWbkSM zk97>~}>cv zb$Jz&BN$J`J1%`SPSlD!*ydwZh|}u@DspA$4$sz zuve=&^SCLUwSd_bGS|G?7q|}mlM8;PN?3s*Qn`LoL_I|_0v+g4G5lm(&>D&~sR6?l znI)Ws=bL^}57Jk}tm&JypgNPrn=57ljDoPx5vC%_rIdlHBI-9tCQd3ccs7 z8t-*ywH72aUrR7)OSDPqV2JeQ%}`Fj)8^<7+S({A|0d~}AU_#mFK*xIuPXctHbR_6 z0>4#tdv;L;zy3>@ngEyuC~{UEld$Xby%R!P6GeG0aQ`p@>*JR7p_5+YHPKN^V4fk3 zP=|o0bY4goP@xf7HieU5*Pudrp}QZK@B~{n6cMl7DMdWz@t^;~@D^eU<>!6(45Z(_ zk$+hp^uOOo|9MRR!MG0pHBKn;ANR0%BC@7!gZmJPZJXt>$m&mX8a!}cI&=T z^1$X1PVvlD`DVXD#eo%T9Hq`v^hcCB+%v=fj3To3%ZWn%=JZC_ zoex%j4J+ zbQX)n1VtYQf2U6; zl+lO7)ctA65@v(JWy3f!Jhj+syx9tcQ)P2qi3?*W-Zw#Ork|#Fs{k`fVV_!Mn!xL3 zIk}JIQwGd7Ve?#cLD_l3;B&IP`k1Ad;eT4RS=pW5A1i9B3J!lo3 z!WN4Denb)1o>9tu9*MQeIgR3$ z0rD%TiSRC-!526-Q_<1bGYn58#9j%95VT-muFHVK2w+EN#G8i;i`sA@UJgGpB~}7x zXT$xV`dKsMX!X;9Ku-Kvd`_&(SCYV;p<-2TVNbPS!mBJ-Wd&_+BDCO7!-ztt23Z4X=cs@kswD@}xU^1g^h~pu=^6pW ze8CszeDle6mmn7p6^EWdfD|dyNB$Hf%@?7eA4}|ajD2dyBKnD5ou30#)271<>qDF}GnvD)t$ z2fj&M*=&%VGF>YIAwtb!y?Ie|YWR?x(XuT5a+5#3i=W?qc_A~KjWxnJccu=Xz$PiiuHzL7#&Jt#VEx6v~-8J%V@+^q|MYi z{c+eNd4k(vCCT3b1G%D0UknFNZ?%lsqRm{_Bk#15n|;|H)9O&HOroVE-FG(hc4&ZE z(2P$V`Y^c7#KE)tx3Id<0tT%cp7~`AFs#cqf_JH!mS_Fm3^W1T!JXma96S=IrQy{} zb0%%7OB-G)J8g)5WpUWTd10Kg^gMRt${vh%)nB};`vmNAbL>TCRA6}wIE<1qWykbg zPcCUTMV-!d>owCDM3^BD{hCpJcQE*pH$gV#ErC;Wx|Pm9SnipSi4GEzX%cltZ8sf0 z4GJEGTyuxoh}YL_^g{rSCj(Mn9xB&ZpEqiyz-a5H?)=3b8E8s zNV4xhy4dT&cqJb_1$w&<_Ly*)afAyxX!#R8gU)gG)(#SXrbXZnoP4uq5;X(XFv+a6 zX>3lBn@9^3=&!a@Iy7C*kVuccxvO@qV6GM z%IEWSgV;mL3SA>lp*KOzvB5IVgDpwgX_;?gI5YK6==zNjtGgy=}3pI7Ml z*K=k&-d*&zJ{n?u+*PW8qBhLLy>UlMZiEIK|oHw$2rs9WFwD^(_d8L4@aT5=s?a8c%PT*VUVg&tO4QDy2SY zjm2bF%vg0dwTFqL)$eqaDox6HxHo5b zNFgp5r*h$E+lpT*h%KuH+&3V2#-tv2SyzkL$JGiwZeF>fbV(hQ2BwSr_!rt3?1T{# z3+p)Tl>z*Z!>MQQ>u0C#>Grq9WuFghUm2<38IZ<^qz{5X#CQaF zf*+9#(YJ9s#v$mL$-q)RasrGY`j8?J&3!QZLlA<|;QEREfPSG;1T6Zobq2^_0kt5q z09VRDG;Z8JCf6j{ENFc;@3BBW=)L0zw=Nv`9rTWlU%SG*pCtHSWjNhK_eeShOUWc1 zguBW=S8?nd=TBUyH^szUGwHcZ_085TFwz#|m8>-DLDz_i63t}Q{&1Hz4#&BBM00Rg zVBLmTo3$&AFIBXyzJFV$-LXKdTj9!w1s4u$sTtwJ%L#eIW7Q-qMV*+xeM-%y0(?Xu zYf$T);aSqS%JCFk#=-}_oMlbLI6SL(vsS@VW3P{axttW?Aj^|nTNjt{WwB<@*PDZT z83dbE=PjR;JkTlb_0}gc$vw%DL8IuHL48?t7bk-p_2$2S%@_`iYL2H6r(tbXtG6$H zi1#UpOr)gY$kAjz^D_2qA(d?Drx*fE7ciOz|S65GQ?@VtM-pB2z zI4+D&hV8ICIAo>$0u9M+c}S*w#r~(Y`X!*Ot*s<>_$|Jy`Jtq%-UyXuOq-?62R=8(;>I?z9KdCKML;#{YLY$;T>XZm?=UMn_|2rJTDP1Hb8tg|jxd^v+7b=!NmtTqBeh&ZS#8&>3NHz5w>{Y4R_ zO^gPq`R-cbRMDwPNbP_#R>)zaj_`d(XF|e#kUT~iLdsnipk{POw`}Y61ZAD0nZ%DK z`9$<-)~~Drk;!X=k_bh1nq3~u>-~rbzMYZ?_?z4aK6~P}R|Rp=V)u!VrbLFxIW+2b z>QCbRY0tN4TkELh&c0Z?EZk3qPr_Z~pM`RmqbUOkJ-FMoK2VOdHC4y-G}8eV+DZWk zX6jN-&=s0$n)ykYm32Cz^-9AHW)kRCfBXP_Rx{TG3mN7#g=+BS3*~Hwshl1}_t0Tr z@>%){i8cncHw7ld83d}Tbd$lY)kp&6w=djR4OnT|iOe!>@!}5DO!8*$5^bG9=g)2C zhntFe*FYJuTv6y}J@zbU^Oo(_A470wLp;z+iI}Hu+#FvD9GC*|JoXx#vUsEWFMWzs zrZu`29dr4^OWAsvC}BUpF4b3865d`bCI=`twM+)7OHA!s+~FKJo5g*Z3)bGBekB6l z{^OH$w2KEi*_gGoh!}k-;;t>d zONzdN&YtPqo8~CDbOb*JqmAK3!_<^zKpEMCm1_Aw;5Ap z5mLu5wB~x0{)K=s#@QHe4QB^QHDEk8EK5WS~XtNf1f;f+>NG|?7@i{z{;oEixJ8NF5> zqrFoEMY^>gJf2r0h7)7!AZa0;Q)Gm-_udiHd6-r+nLkdP8Idjb7YZHg0a|P*pi7*?SHZmWTU_)ek9rzu5jNMxZ1-PQ*8;dpg0KMZ+ zvg<$xcKwT1PCU?+SNM$wAHJ2tf2-A$Hg|CNMu7i3u;2Rm|Lb+l{H9sv<-UiSxL|KC zp<+^oL`w;+0@uOD5|ltr1!It<>CyM9qAyLPU7^`<<=sZwJj}lcAO#Jed;j1|xZP-) z_$diC9(R?o{+&~-z0B_J_6ANFjEe%X=ZqU66Q?A1(h!AWTU?EZ3$shuPcfd!pqaK8 z!fD0;=)T-Z(rPPKxoI++8v5w=@#2 zMjXbSXl5Z|#_JGO8fUn|tFn|N+D7@TQwqfCT14gR8eKfo(XD8)29;&w))lNX3C4^C z4_yvO`*Vokel4~CYWw|m?mdP`6}1AN$VtBqzG;7rd!*;vK*TA97s|PqHCZ{xFnm)~ z9s2x4@urFRS56_BvH!qM3*$k#n1pR|IB6|zmWY+93=<3xqmsN1=9s}qAI$)aN{!JH zA_;b-#~mdM`1_d@qW?<#VVuI_28>DS-W;HRhS3j+m07d#0Xp|#ZnIhhr8t)5s_EE` zT3JNF4UnQUH9EOWEO^G^5&wflY#veqIXg;kE-My3<3l<9gfNQkP1q**CvbxQNd9i4 z?}rC`rg%nf{cI18sklEK1$F*5M?}!fAVS$8bbE-G#XWNyeA8y{>>3X2v0d-+Oj2Nm zDM~hDkKQMEUONW4)V08yH^lSkurW|St2O-qg*X|7z@2eK@Q#PRzc^?S&VF!iHkZ9r zQ|_p96s8ueJgP3de8T?u*X4X7*PB1c+u43Z4}DJ|zhVoT0A8Fiv)KyX%2cjV8ZN3c ztL25YZ~Q;dWu@}E_5AmW*7O3qy%ypGR;@9T0t)F($+h1UowgLH!l=2w zK!qu7u!lkB2db9ff@F80U3Y&HLxo6uuR{t-k=~4>KaMap`91+%-=X4x zPIjb`(iwV6mt`gQh|&>5t)M7K(0ED|DJt@k5JMGy`CcbL;4X9eMpYv9y3t4yjy&B0 zXf?}(|7;DEY^&|$+8O=?lHh`ed24Gb-U*!6TTaZ0@pw}Q7YzJ;?~UHyTPQ)J#Zvh? z@zWJEmhvLkp>o(em;{^vHcBnExu;CTR9eB;(I!)lr!hG6E{)ZFyun7Nb=JW@0qs@d zEkQlh4xOnd+KSSjO@HD@I=o=|<+>iix{rdun$Lsk$f(=9m_IWJCWN&~H&6?b*q;D~ z_z1*N#2($~+O|WY^B2XDwT~$_Z>S36GLjfaX(W-3%cth0B?O@ffccd9nP^2UYXi03 z4uGbbTuq5S1&7(wk?e{h zVAQ9y(!U+Xu-73g-D=uy!XCaY0}{*g46Aw(uj3Y^`bK2@ecVX7t+Z{Sba#VZYI$;U za)t(vXQ(p)x&2Z1>e|kteyh;gzRHrGHZFI%Py~Mt0qoEdxHKWd^)3)GmjLTWKW3do zAjEvy9GP>k;}a@@mp%Hf?5FySdRRTR601M)xPFMIdDtwb#x(F{<^lxbF(}O2M7WWp zl2Z1I|46W47x`fC9WM8*U=}&;9?~EtEz$n{MNV}jhKm(Yw$~vO&R{W4Hb*>XipJ>;XH2Jpx|a+wMXI;lt6wo3Z)Ljs`DHXyJ)$LIq``b zD^gxc6cys%uUQ7+5cWzYV*7mU@Rfg|8&gPjCfdIbLD}~qVEcDktbY!{zmfonO8n{L7g&g|Bl-aN0_nVe5{2&8e+`xB zMjki8%CJ(Aq9@AD?tZ1GGLZ5Aq1*=~L5L@!tSX&ponNexPDz*N=h8YKH9L-P81rF9{!7(z-F7_b$_>=@tomyjdThM!y<6Bae zY{vdG=_1{p8)N}8ioS;C@(dr@R_)}T5C%c>V|b~c;5LhRi;iAu8)R}ulL@=&s@Zk6 z>}ySWoQ>vDwvcTPx>kHaVbZ+SX}@rki*GH~J4+^t9PC z=u|fHt=14)lle{6cYvOX)mZ&GBJ2{g$@KN8b~e?65RAYOh7N;tzih~EAExjN@1q+I z%{fZHMf2P&Y=78aW10S)9?~lu7_`s|<`1A++aoC^NWXxm+jurhppAHvH?dRhvT4g} zhq=&!vD%Yows`SWp3OsVWit8a_qg>5DDv6w@3>Lm9=CAtDXgJv-m&d;~GjW^oz$Nk(#o z1@_a2@uE@10q#}vxN(esT?KbwBA8PA?NrPEpYyT)cg5-dgKbER+m`sAk2Ta?uU_9) zg!RR|*tAsgGaqGH!bakI{!w92PLLRFM>=soXI*OIYUm4;7fv+@-Rlppk~yYy-;f~Y zcJ%Gk`t85CQyCv0$GhmhL<<5aHHdw~BEFM9lm%|p%#Hbwp&mQodTollzGque(8vY{ zR52gtrQ4dcCO!$xA&Ru#v!AX@CL$(HRaHtn!s|1duc@egD!o=UGEWK_r5cS7tNhs` zXU)qVDM>CVNreLwc-GFA*S^Fo;8zo42_DKC(|j8o_}K(;FZ+tK^h}zcEzqyTWWgS@ zh9q-VNo7ZrCv?L8M>F4XBPFc`LGn%7C|ap&BD@1pRflYD?8kcG=Bv?7FhDcF#Y3#* zBRajkVLtbCw0g{{;BLZUXNXE4Z14wHVE*azZ*o4JS@ma$C)d8`c`ZbJk2~_fGvavN z!>{FFkFc8!sb3(TVQQgHCSQ14xZrpu4#;GuWJm0@kuVUqKsRotYGY2ARIOEe##N}v zbX>=47@whw*!`#5H)A98{>QVNI>*K~_FtOT@KY!+UcqjB1B4c-kBRlkrvGYy$QybV zF8{s^o4$h=|CZeN&(Hsd7yXB2N>uui`3|dpKDi%`*(GRz2+1RcH;9hQ4`lzsvXF{^ zASDO;(yU6hckQ&eg3FKILw=zn1_~wR^}Q~zbJj$#j2DQXx|*2syq}!7`gpznAoJzm zJ{9JZ${c8jVh$6aDWuQe$D)R<=VV3+B8O&3?z7tEs@|;vc)&p7En(D+ufG#Db6+i2 zG_pH>tN{ti&V+3C6i?=zx8Hu>Rb89an+j^Ca#Z|_`WR}?UZ%#yU8jLIFGa^8Qht-2 zPIzqsHkga93Dl`Ym)3uh-Nbi}_SsrnFPardtK(KG0R0Alo=5;j>-W%a zv;YBaW_n*32D(HTYQ0$f1D}mzt}0b00pREwqaDs63=9t4-W0$vOrgWA$;f-Z?&gN` z#Y@8Jh((?U{Aty(@Y^H#kv>kR!#)il7cQQrqnK(M8+N!FX;TKysz_yWVeZyih+bxz zPFhwq*I9wiJQZaX@R@Fd zhm)M^g4J!ocM&Sr#Je(})eKrZfmJTtsBOj#%QhS~p?;xq0xat>K!`S6yqJ+fOHe7RiPEXH z=n0VtGLibuH)7tE89ep3(GVosQpm zp|j;a@eEz7Rpe-uw=-^hN9oU9&rT-Yo*rL_J%lQb4~8PawCJ#I-}SFFF?tvaaBG!b zTBym%9f;9t*5>+-4c`T6gEj75YQhMztT$#gMLkh}wXQgjGilvp^{t|I(d@IA0>GVn zVpcietfni2yDnL&wq|Q@girp$h%7qMbnk`ys)1-$xqmNOeHiRAOobh0h4dia@LIh{ zy#XGd*48bZ$YIF~Nt-&b2;LJ)iLy;M0aw48LMd|`3NK3}exvO%Kva$Hkbmypq|qc`#aotE2e&8Cg`toXsxK7lp#v2NQs4T)#v(*T` z4V-l$BJ&{B?HBmT8)3|K-ss)Yn$YH3|v82T4{qFo{drP++b-XdQ8sW`iIaxs@bhmv(W2Fxcau^uSMsEK>Rj z73{pi-93B=GkRE^q(gv}Me`lRD$4u##NtahUMW~WV<_G(mZgpxEkT>ktO&T}AiKv) zYPQQC9FaFTI5u-gy3R1+TJ&fCfwY)wTXYdcPDt(be=m1EX>Vna?{aVX*1{P79o+jr zI=)23ZJRl{?>rL)3bcdo`T_?kA{z$wVkc$8Dd{}$~`4ejC5hO@{QnXc#T z0QlFBFY^6Xn)J?tY@wU`ojVNF&?|( zbnfCK%xS|Q_1F^Kz7K?C~u(8lI(naxFtb;QU!&?z02`H&FF z!mkS)m6y@=PwvK@>EsMeD+WefGIOsvHuV@0?F+bwogS6kg5}ae=zx=nP;tE?I({Q9 zVRtg!inDjc7#8DG$VPEZA`5Im)BVEC9nv_2iK;;wK}ioH&CPgGbexUQ@(Sj9_!r)kvXCJ%encU1>SYu&bJCU4kM% zu&#jOS{6FHo~6ie5+zx|y)N0k&eb>APMu|luTQ!uedH$Hsv?C|)pDP8od%Zf@L%DB z?d11_^zWLo_?E2r{+*gqwzl}c2v(iS;|kx#LLQem@jm+B5D2$HA>`r^fywY7wJ~#Z zlu(rd>NV}eigu2Sg3_d8bT4$Y1!1Cz(0o0K*t*bc)*B~uYRT4w>&?@r zUBxz}*FN1|;CfKaECVr%Gk{uFjmY}Z+SHu@@koWD{1&W1mY!%e<_Q}MIwi={u_m2rB<#9V4J9>?*vl5oRZfXJTmY|e!7f;(GLTw$3dyXdC-ur& zs_ZQKr0CpVi2L-7ErFzqvnpB^fdXWKiYzKQQQ2%ZnB1O5i8%H>MR9pfj2#q3(f2sp zVrO!56^9YP@>1p*qBZ4b(z8B}iwWo#QPzJfZ2n5J5;l5WWJQI2))jQh@YnAnpn|kj!GlSHn`h1%4Pf10 z#$`L|cVl)t_`K}u(j}W>gTh}T{@E_S>wj}-5oWCtG&&=!2_|H?_mnV%zl1v9mRA+J zCMJ^31?>7-WTFszA&y6w3_lSx!8<+n4o@pN{Lvn?<(T0BQ29+UM7(g`QwA~LQZnP4 zU<-r)B?xOkj>kLd9>>fmqNQU{&&ZyHsS0l7`|r20kw*Fg+V}Ep%kOXy>A!Ju{=wRr z>gIY{gR!3yX{l`P-^*cF>v;4mcY)877@BGh6?uPPO0p)^#==jixyOm%O^2i+HnD$i ze?W{vh|)s_^3w|j@ozPP_FI*1=|dX1LRy)u(_anX@r5O@{4qT2{jrrkJ8^;;`Yz`p z>!R$W?6kPNC|ix|@r2;3ey4=Td0YGEQ?Ht>j(7H!;}2=V^6W0W$^`7 zI4ep!?~O!v5~B<=*F@yi7{w_Ts5@e*KyKL4voF&)g4EC{VF$Szr8e2F46~Y@w1hMV zB%|OUt0FB_LN@$5!IPUVer2bGG~Q`Jtd_L+EQLyuIkjw*8Ta0}ElPt!T7GJ#Kxo*& zonOLfp)?We+vTM-Y)^7ym3oj22{2xeP&!pdpt(j%`AtU70i5Ar?K>M$lchY5>M(Uj~|*+YrLz+Z9N3Kui`=?Fe|1= zh!)mB7k+gDHRK;^CKd1GKRWJjSI>*YMszDj=op$RO-x?XI{$YHU5cHrjt6NIvle|B z#L$juDFK31N_xp**g>|YiJyMW_!Wp>UXUE`c*Np>XD~WQ6<0EWeTxkBn;XiVq$xQnv48#Lm*K9f1Q8ZhUc3t@ zaByP4iMp@`I;U1fwS$bkGAwxxx!D;{Fr(r!oG;(WaktP|&V_b?=8BQmip6Luj5$0| zhc~53_*^ZlbQ-2(Y8FF)29@X0^xnMcQ5Se~#b*hLhQt+n2DLTSmsT`OMuM0oSz=k* zm^XohSF%XMksLI`ycclL8ia^bIX9+^&a4uqXvT>sPv0wq!P{{4E3DjB=sm@V$Y7%! zC+sm1RYq9hN$~{yN{e7VltX_cA)c|!n;*q?dYXczgf!fg(noPLrnnxesgD==To z8kL8^Xe6-n;aMKLfz8PlRF#MSv?4>??F%vaeY|2;u^2((FqEY{<}^6LdJYlC1ZqB3 z2{oA5)w({3mp4GtYs<#=m=-G}^`WExESws{F`1^KHG35pCaemZYTNP4S&coDVz1)h z8*Z79OCNUVzXp0;MeWe`E?DxliQF|%2gv+p-JXPDdv`g^VtVM@?JFJ?P6J_C73sK& z0ASccOU!}Lgai6b!cl)%Gh6~G=;U>AUOIwkc2>p3YGZLOhFEDwM3HA02;!~cRX5T<+xEU;Np547z(7REiT>>AxDj?=02(=YF7$%UbodGTeWgW)mhUq%ohVGsscH}xZ zFvAmi7P59!*J~lG8ifrnwf6T!fOnxnfy+8QVkBu4a81qdeDepEiW>$<4BTR0#DoQW#Xh48w zkOr5#77d`5aa;OS*H+0?*2SoI*}r^XC-_7qOqyh=csx#Lg>hkQ;q_?!}lL-SJD0?H4&BRTO`(T7`&1=fH z0g9@7?8b;wGwu11oSm{o@(2a)+v}dEcFaqdFJr`Tp%QNrqmIDFSa17nefwd?;NaEU z(#gt`FJTu}HP<`XFin|1%8^^}AmpUB1EQQ$c0SzBm)=_Eg<(8417DwupI)rljtaNr zZ!AN8cyEV!L^3VFlg#OVE8?Kq_gdBKK8{@L9YI6kM5O`k4C2vLnrurQ>zRO>*pd){ zz3B0|ccsUkB^<*IiL?N3Kcj2iHMHJbD41!e)8V1H5xSTc=e~^O90+yHjLh1Wa+A!h zsoiZ6;mE2e)6``%fiuL#d5-M={fwoxF9fU!#-A*n=IWKM&w6fl-e<0p zdsn$Tzxt~Hkl3`0vvVNwF?#PRg}gj1OfgXZX(wfV=*t!t0bR$4n!F}W{m&0LlNF>A&2Jm-taK&Yln0GU5z zg!R9P+|Jc4c&$~?;e0^r=y@EmV%*K6r^IyM+Jo+v?U}Zaph@_=ol40*wb0{(PeHbw z>xTsnVu8b9`43^L!`Rw3ZM>{%%-%P=J3nCihI4UopHu_=f*oEV;eU>t>SB?$kzDv;~WH^`S`elYG z*-6@0jA_omI-bj}^^@vts~0>)LPgL8s+ErVUw*UB zn`>FfTXiWa>Yw|TgrdG!mqU0}+vBytAJ2b>*|<^jXExZ(40s1!Ut^ay;5%C{%nu$2 zbZvhO{fsa>86G*RgW~X&k394u-+}H!zIo7Z&};6f5()C}?n}|IG45FpuWdi9^=+;x zLEm@I&%xhMM?DW5^0LP-2JU1xXOkf`?vdP!_h6`9Lce+3LqXD#@fSzqSMJfQsX>po z@MJYcqzFT;M4JJ6KWrV@<4Ke*#febLn_ z>w@cZkC(cLHm<6wz6*Xncuo@WbSZYya>K>a#F$Q|dc{UKB&?WBzW0e+N)Jg&82PLQ zj>?XA{Sm?dxM?5gAqP{{fM{M1+0cp!ZwQS$68d&|B}{jputRd}xdt{nA9Q$@l1OjN zwPBRPEZM+OjDqt}$}*WW&=}cSj4W?1h_)37eOx+ZRA=B&{?i+b>yYDNWV}UbYk=)Q zP>aH+hvg2lDxPoOodbaFV4spi`Gh}cc6QhgZ_BsdPLKH=`oZCekYCCWnS}93Y+G@} za!L0GzeR8iHDvG>isJs$IH~dIu+43%6sAgXN?`AKa`S4wTD&sOfq!yL+ooa`CK*a5zP0v<5_Vz--GC62C>eyW3Jv6(Yq3-K%NWL6Xy!!|CEm|)Mz%W>E z8o}p}6cv@1RSD1*Et%D)=A1BlM=CzT0YvvVP&fOXK}KZ{D8k`P?nVeeRZiT)*pEM% z=FU_qeKs+p%;7KvQdJQe#e{H?@5!Jesxq)<)e46sH(6w?SKJ)^FkwkxQ^6~{Jy>!L z?-0%cPaPB9Qg7@EGm^=Q4d9)a>IGPIM!an+Kj=s0)XsqsL{vM{mxvH33e!z(xV#6{ z`Ke{~DFS`$k{wC!l};Mz_P4M{A9wg2cg30(J!DExlI6~DOy0jNOTs*m^C+sdVS>|8 zKQbY|-cZxXWaaYAPh&a(6n8nMC$E#4Ax1dG1^7U`kbyP)eNt<$z# zeKqf8_zvmg@OpT5%}K7@-KjUNJ3r7^Rf>FD;loeDy{U_?lNQ`5X zXHyC%i3!D^8iGWLS`tcKhJXqJ60@d+&adg%I-N)y%VpG8B@euw1mA7gj8|K2kPH>G~2^m))x1XKx$48W}sSyxP{S^wVRF|HV zSk#xKrLp;$DhJ9vDqaY%EILEM2Ie>ubBPA(l^rv|ENJbGe@9V+j@`0`*N(IrXNb+t z205{qs|n4g|1uYbn6-A<23RGq1$3V8EW-~7xP9?syH(BlAPhezomNa`j4br9Fz z)=~FT)xlItaCuX3-KK2-mJdlf2&(s_-7;NWiW66eC_FeWNyhAkMMLJM8Npo?+Ozl3 zBevk_Vd?ByzGrXwCsVhv6s(Tp+}Ppw3y4LwYlS3-2BbkP8R^(QNOla#O~s?%vbkoe zBg7QnQr#UJByEJVsd2iM+}^v!s~Q^P|b?a;Rxpn}(?tsFwEWKETpFp4?3BvCi5gy4)HQYE#UD<7N|{(C=aHd(2(eQrshhDxlelF8qM>` z?!0>eag8!)0GMz9P1*xxHa$t6>2EWBNqBCD`#9Y24Ad)Tu`6xK*_p{(M;4Dbj0LQy z%O9jFpEv&AJWr7I^R~32?HCc~v6<%wf!D(hX9T6A8GT&3cqG%Ov}t_I^NJRnkCk?) z40aie{3tP3S-krhh($@gBH7JJs$BGY!0`02RLo%7Lxm;5!mS%1%yUC9v`4f>ieE4H z#l!OqX^|s43*g(cuhNd>V;JW(jq>3?_#5Zu!R`cQIIF)&sZ$kIb0@Y*8LZGeMsTds znrK>jN8=W3HoVhJ8%0!N;w!@&QL5YHfg-HJ%tTy__Huju0)K2$Wl{|%)5`w*z1p=m zqk(I6-12zJ=u`GR8QMYSslPAtZ@0EflK#cS$XoUTvUzAD5C{~PM{Op$pD8|ftE~PX z{g+?P+@KCOnx(#?cP%8e!)k;X?=ysdA>^SgL=k26OVx%=wa~L|(d(mYv!{8dcze6j z_h|LI<1^Y z5rl?QRzUbq<^7^<3Nrw4iZW@%LvB%uj&Gr+rJ~GIy%hkFrYABRAUnS$q%D0>;?e0F z*YC*NTZCx#;`B%J6dANYbnJuKuiyJ@rPo1!W(yoV9-N|E*bi?ZPSQpCp{sJ6NZ*CU zkKUycUA-@@e-CT-x2UC~bWalsYqBGg!6ArFWmEw1t)0(NT zZ%ah9P*p#+ogxb4pG<{n=s1{w6yf)5Pnc7k->i4J$D=#oy!(LeDbH6emaBR=LFm?bmTzLCYIaUSX9i+(Np3Ech~* zZHTPZ`qMW7@!C0m)ySk|8>=iz9uk3a={c)1BmX_(iy>YbGwBzbB70ITRD;4)n5Re3 zv3feudeh@Wv$Z^3LRkfij>W8`O&Xe0GmItv={wtBH*eWd&MAov7wPat zRX+eoZInHV$FwzpEE#?ASl&^}UDi!0=un=cDFEG_WE^xJtRnhKeVAkBcPLe5t$F(B zdMxkAZQBM_DexyTjp?KgPItFnTep?d7nJi;%7+2_B3wz#V@$6<-6N=m@0Eb_ma<*2 ztl1m5s--y1ew_AvXWGOBMlS{P^oSw+WJ3-`l?LTUxly?Y@u^I6d#dM}QeckO61;u5 z*oLSY({aV(R;c;E4J-16B^vd3ZXp@#!TXInjaahq0>{!8;$%ZPqW!!dTfeZcQFyZ1 z>`NnKReAcFyh{VoCo(Ecg&r#L7$AT&J50!dWuZCSI$7O;2*rs6tQS_bbKP5x$#Btj|uuR!tp8n*%I3T z#I*o#zgxZ75dLNmV{k-117H-Xi89zDKYCfrph%G{*9i8aW)#fi>{Od&bOn&EF~ftt z+7Pq>z)@g8x%{iNrNriHjL8#Tcz|$oqk6D3K2kKbzn0Hlx!8MjN0IXyEo3x@M3g3*q)7 zf=$>mM3McVz#U|myVoDXx{f+xFGNmwCa95_dZ&z|Bvtyn?%{DPH&dD&SoE3s&_z0x z;~M43AnS-z%h+87s-#;(dqrM5{(uxI-x``q{p*WxUWkEWpcdlud)Nt*NWi7ZdDIrC z_*E;|%V30~wZFY1*p<%OpJEBchiO-F5;>!XwzZz1kddp zLZ#w8zx>=scB@Ztd0c#j?z|9PpBNz*-EK)g4%Ib=AD#i#u%c_fz|}vELP1yJH;%_G zBIz&kcdB@=G(LXklqV+FuusvJHyD%Dgh&vGat^kil{edhO2WkgZP$cFd57ALEfGEm zA{ooH`(!1zw_6z}?LjLUIq8nv7yXTl)rjW5#`YLa&C~01FLasqF-bD~i?@MUFJQU& zSK^=jJ}|QE;-6WsfAZ7xKB+J(n3l$B6d_yYh*tf=XlZKuwE1eZmsuk&H(f!fH*$*- z=8VRBrHYD*9hKoEhI<&FNX$4HtbcL+-fc8Vrj^C=axFkI+|CN6am>_(t&OL%n-LR| zXL0(#i=SzkCh-Z&b)93uyM`NMyhTR&m(~3<4n_DN8BWx=fa0lu|1Wo@HZ_;#WnRA` zFqhUtg=`xdz#g5)lATxmS6KhH?*TGIn9kY;$7BRg7*A5X&9B*MBPkOrMH%aA`I`Ybng+8#5_=~W4X{{&s zp|@|-*oP4uBv0IA7toH!!d(J7dy@Ny_DjwVaC~P;D|)N5{HHp?{K9H-kn(a+Nk${B z{~CaG+Xi)9`xa=0zdbJ0|5IlAA7J1gd)GgZAo4rry6_u?XS4cB)X(^@9Ed(@ps{>e z$;(f|5Hm3q2K9j6W_=e0u=dNMOQhZ68_T_L_>>Y5@dZ<#gj*R+J$2&S-1*dXk7=Ic zjqk;++de;1`r?`E$jeg1i2Mzpa9gs94gq1K#1G6!EvdaUQY3boUDqWoRNM3Rt;Ks? z|EIDufroPId>lu~1>khSb`Z}t=!`zW%eR6~<(n0XDNNTWf@b}bdxZX%T;np@o~ z(jpSKP@+_Hy(&v?mP+^bo{8~rj4|)&GoP_^zP~ePd(Lw_=l4G;fL^t`kw|tiVN}*L z&USsIm7Jk{c%)>R9*x(!@`lVOub%65yrN#sRP#t;S$u}Rid7@pCX|9Mh#q$0D>wVy z`ks^`e)vp6hryw}6~U=;H&Wd3y($#i=Gfb3f0I37m4Co6CP43!Z(x-N`X5osp1tms ze%c3}6kDxdVi;xvDg5Kk=TLkvqlYWfL@LvboWsVW+U`h~6rz383{`x@j1I34O>A9u z(OF!w(7xw%ab7W5$HpM}K%Mf9$YGm+jk=D;r>mTjH9CcgYjXwbLtab1OI>AUy5g{C zP+qH{X$!n|DOCvC7Z1h zLb#ijLmCEVemlBALG`lx+>j-CJM z{h@xv#Js&KqkRhBOy1ko*g1^9E1Qrp(!v^?%anZ^SMoN$#p>Wa#eciXlWFTD1ES($ zH&V4-ltR*P33%k}#G;=mJh;o#As5=>+aU21_EK|k|9@jb19hYPwg}ym-xdxYfL#h6fHhzqHN zYkcGRSE)zjf>t}WM{V$3mj0`ekRsBM<`vXf`EFyewPD2G@^lO3*a69qCC@P{(GljB zE`En-IER~AWiM9AR!j4{Uk=#yOt;C+#-Op<(;EA!y|FJxLO9WFXBeaS><3EcaP&*( zzo~{Dmbt3xpYxQDABzsC^mB-j_Y4fixsHDJ@(yo#wk?L1;9ELcW8OHntM9o~DYh@8 zuPLcd@fq&(3&k|dQ~tzN!->&}k}9$L;?Dn7wRQCA2?Hg$*v-@qnn$E{Tf&&2xYXs+ z_LD(>AN;Ua#b*3^n-u!hwIU%`r>>7{oU5eb3t#wbl-7!T;3rgjJ92pfS?_rEApy7Y zS9*>cy#}|gS#39hFKYTV!#^#)X~5`sPNONB&!GZCky=_LR?Jg)3KK5)P-{=pn-RD7 z|KV4UFm2h_XU&_LWA-qv&zCnd!%S81{Fg%;N=8@A{_{GzSaQPzz=BLBF>Q^P|%BeNnwjwq79i}r|@D4J&`6WOqN zeY4?>G@M^Cmc%VrU_17)(9zUH(3Np8iJwT-!F6ng7(=exsw5C*3 z$^`UBU)w+AjcY3CzPctu1(Qyh&@|3*@)ERG>GdpMP7qb49B)w7x`l3AJg7h}x;0XH zOs6_OLo-O7?~z)8VTm_**C=p9U)bW;@Ae%!8vjrG)&fz`lo;@0df-oa--Bn=Is4xK z#g*H=;%p+BqtiVPugD@`558mx$YcUuh-p4BSDQ-0sDU59vNdxwQMcM|u4!j8JDY#` z79(TupPA21fk;WyiB1KNgrKIg*_v#(GB2B@A%#i?(d?zypHcFT)lO%(98W6yOD8?n5M)czS{wx5WqGz2>X%9Wh`BayD&NpQEt}Go42UWTnwA<_|%>>Wwvn$^e4>v zR$*TaG$)R%LWU<(G(D&=EHM@W|V)P*a|Qn z4hw+b3E`aZ&|L|Ph28KG?7aw1*qPfsFcbDhMwm-!oR~lMl;&Nk!8XJQb&MP8{HDZk z@nIuXL@4_N7sa1zs|pLiwv~uL@+mF^IG9+%O0bI^qVyq&3ni{R?O;vVhz!xpO5sA2 zlPwu61)H)UQWF_mNO7=eft6tY3qjn5ACL*xp{QoJiP>sQd;1H>C zumXmzaWkg(sYz|Yx`GcxA$*%sF8G{}N5KsPpCLiSqRSQ*W8W6=(*p?eRqY(+kLsBF zECF0j_>T|>v%g_sCZ}r@ymgC^g`4J*x!=fzKLNa*i0Hg+o}&Y=W@mJx1uo<878fG( z+vDkl-FzEfaG9BzS*t|m?iMT2se)iLW5(_odEUJ)I~zW5%Y{PefPe47&D?g75rz66 D613UA literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..6ce793f21 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..2fe81a7d9 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..9618d8d96 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..951c02c01 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'aws-encryption-sdk-dafny' diff --git a/src/Bench.dfy b/test/SDK/Bench.dfy similarity index 100% rename from src/Bench.dfy rename to test/SDK/Bench.dfy From b5830af5b8f8b55375f32014f9dbc50acd85f2da Mon Sep 17 00:00:00 2001 From: Luke Maurer Date: Thu, 19 Dec 2019 10:35:12 -0800 Subject: [PATCH 03/10] Set up benchmarks for building, running on .NET Also made the Java Gradle build work better by declaring the inputs and outputs to the Dafny compilation tasks so that it can skip regenerating sources. --- .gitignore | 4 +- EncryptionSDK.sln | 14 +++++++ bench/AWSEncryptionSDKBench.csproj | 53 ++++++++++++++++++++++++ {test => bench}/SDK/Bench.dfy | 27 ++++++------ build.gradle | 16 ++++--- gradle/wrapper/gradle-wrapper.properties | 5 ++- 6 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 bench/AWSEncryptionSDKBench.csproj rename {test => bench}/SDK/Bench.dfy (84%) diff --git a/.gitignore b/.gitignore index 9dadefe43..076394248 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,6 @@ test/**/Output/* /test/bin /test/obj /generated-test -.gradle/* \ No newline at end of file +/bench/bin +/bench/obj +.gradle/* diff --git a/EncryptionSDK.sln b/EncryptionSDK.sln index 29159c7ac..d980cac95 100644 --- a/EncryptionSDK.sln +++ b/EncryptionSDK.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSEncryptionSDK", "src\AWS EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSEncryptionSDKTests", "test\AWSEncryptionSDKTests.csproj", "{6605AED7-ADFF-4F81-9DE4-44AD3E80B0F2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSEncryptionSDKBench", "bench\AWSEncryptionSDKBench.csproj", "{D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,5 +46,17 @@ Global {6605AED7-ADFF-4F81-9DE4-44AD3E80B0F2}.Release|x64.Build.0 = Release|Any CPU {6605AED7-ADFF-4F81-9DE4-44AD3E80B0F2}.Release|x86.ActiveCfg = Release|Any CPU {6605AED7-ADFF-4F81-9DE4-44AD3E80B0F2}.Release|x86.Build.0 = Release|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Debug|x64.ActiveCfg = Debug|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Debug|x64.Build.0 = Debug|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Debug|x86.ActiveCfg = Debug|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Debug|x86.Build.0 = Debug|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Release|Any CPU.Build.0 = Release|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Release|x64.ActiveCfg = Release|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Release|x64.Build.0 = Release|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Release|x86.ActiveCfg = Release|Any CPU + {D105B491-CA9D-4EF1-8A1D-EF17AB0A5876}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/bench/AWSEncryptionSDKBench.csproj b/bench/AWSEncryptionSDKBench.csproj new file mode 100644 index 000000000..da857286d --- /dev/null +++ b/bench/AWSEncryptionSDKBench.csproj @@ -0,0 +1,53 @@ + + + + netcoreapp3.0 + false + false + Bench.__default + Exe + + + + + + + + + + + + + + + + + + + + 60 + + + + + + + + + + + diff --git a/test/SDK/Bench.dfy b/bench/SDK/Bench.dfy similarity index 84% rename from test/SDK/Bench.dfy rename to bench/SDK/Bench.dfy index 9d8ae10f2..c68e97362 100644 --- a/test/SDK/Bench.dfy +++ b/bench/SDK/Bench.dfy @@ -1,16 +1,16 @@ -include "SDK/Keyring/RawRSAKeyring.dfy" -include "SDK/Materials.dfy" -include "StandardLibrary/StandardLibrary.dfy" -include "StandardLibrary/UInt.dfy" -include "SDK/CMM/Defs.dfy" -include "SDK/CMM/DefaultCMM.dfy" -include "SDK/Client.dfy" -include "SDK/MessageHeader.dfy" -include "Crypto/RSAEncryption.dfy" -include "Util/UTF8.dfy" -include "StandardLibrary/Base64.dfy" - -module Main { +include "../../src/SDK/Keyring/RawRSAKeyring.dfy" +include "../../src/SDK/Materials.dfy" +include "../../src/StandardLibrary/StandardLibrary.dfy" +include "../../src/StandardLibrary/UInt.dfy" +include "../../src/SDK/CMM/Defs.dfy" +include "../../src/SDK/CMM/DefaultCMM.dfy" +include "../../src/SDK/Client.dfy" +include "../../src/SDK/MessageHeader.dfy" +include "../../src/Crypto/RSAEncryption.dfy" +include "../../src/Util/UTF8.dfy" +include "../../src/StandardLibrary/Base64.dfy" + +module {:extern "Bench"} Bench { import opened StandardLibrary import opened UInt = StandardLibrary.UInt import CMMDefs @@ -45,7 +45,6 @@ module Main { } module {:extern "Time"} Time { - //method {:extern} CurrentTimeMillis() returns (a : nat) class {:extern "Timer"} Timer { constructor {:extern} () { } method {:extern} ElapsedMilliseconds() returns (a : nat) diff --git a/build.gradle b/build.gradle index 5edb38f7d..276b32d08 100644 --- a/build.gradle +++ b/build.gradle @@ -80,24 +80,28 @@ def dafnyOpts = [ '/compileTarget:java', ] -// TODO: This should be using a custom task type so that (a) there's less -// duplication and (b) we can let Gradle know what the output directory is -// so that we can avoid regenerating sources. +// TODO: Make a custom task type to avoid duplication. task genSources(type: Exec) { executable = 'dafny' - args "/out:$buildDir/src/main" + args "/out:build/java/src/main" // Must be relative path (Dafny bug?) args libSrcs args 'test/SDK/Client.dfy' args dafnyOpts + inputs.files(libSrcs) + inputs.file('test/SDK/Client.dfy') + outputs.dir("$buildDir/src/main") } task genBenchSources(type: Exec) { executable = 'dafny' - args "/out:$buildDir/src/bench" + args "/out:build/java/src/bench" // Must be relative path (Dafny bug?) args libSrcs - args 'test/SDK/Bench.dfy' + args 'bench/SDK/Bench.dfy' args dafnyOpts + inputs.files(libSrcs) + inputs.file('bench/SDK/Bench.dfy') + outputs.dir("$buildDir/src/bench") } gradle.projectsEvaluated { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ce793f21..8395f0741 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Tue Dec 17 18:31:06 PST 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME From 9a61a03deb1919b78461ffe2629610b9ccbc6b9b Mon Sep 17 00:00:00 2001 From: Luke Maurer Date: Sat, 23 May 2020 06:39:16 +0100 Subject: [PATCH 04/10] Java: Adapt to unboxed Java primitives (#127) * Java: Use new DafnySequence methods for UByte wrapping This essentially boxes a byte[] in one big box rather than an array of (potentially millions of) UByte boxes. * Java: Adapt to unboxed primitives Dafny no longer uses Byte (or UByte) in generated Java code. * Adapt to change in Dafny unboxed-java branch An array of generic type is now represented as a java.lang.Object rather than a dafny.Array. Co-authored-by: Luke Maurer --- bench/SDK/Bench.dfy | 2 +- build.gradle | 1 + src/extern/STL/__default.java | 0 src/extern/java/AESEncryption/AES_GCM.java | 32 +++++++-------- src/extern/java/Arrays/Array.java | 20 +++++---- .../java/BouncyCastleCryptoMac/HMac.java | 16 ++++---- src/extern/java/RSAEncryption/RSA.java | 25 ++++++----- src/extern/java/Random/__default.java | 6 +-- src/extern/java/Signature/ECDSA.java | 37 ++++++++--------- src/extern/java/UTF8/__default.java | 10 ++--- src/extern/java/Utils/Util.java | 41 +------------------ 11 files changed, 70 insertions(+), 120 deletions(-) create mode 100644 src/extern/STL/__default.java diff --git a/bench/SDK/Bench.dfy b/bench/SDK/Bench.dfy index b20374581..c40d20b53 100644 --- a/bench/SDK/Bench.dfy +++ b/bench/SDK/Bench.dfy @@ -63,7 +63,7 @@ module {:extern "Bench"} Bench { var keyring := new RawRSAKeyringDef.RawRSAKeyring(namespace.value, name.value, RSAEncryption.PaddingMode.PKCS1, Some(ek), Some(dk)); var cmm := new DefaultCMMDef.DefaultCMM.OfKeyring(keyring); - var n := 200 * 1024; + var n := 1024 * 1024; var msg := Replicate("lorem ipsum dolor sit amet ", n); var originalPlaintext := UTF8.Encode(msg).value; diff --git a/build.gradle b/build.gradle index 5213e0084..ab43995a6 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ project.buildDir = 'build/java' sourceSets { main { java { + // TODO: Properly split things out into main and test srcDirs = ['src/extern/java', 'test/extern/java', 'build/java/src/main'] } } diff --git a/src/extern/STL/__default.java b/src/extern/STL/__default.java new file mode 100644 index 000000000..e69de29bb diff --git a/src/extern/java/AESEncryption/AES_GCM.java b/src/extern/java/AESEncryption/AES_GCM.java index a25754e0a..893e0b39e 100644 --- a/src/extern/java/AESEncryption/AES_GCM.java +++ b/src/extern/java/AESEncryption/AES_GCM.java @@ -1,53 +1,49 @@ package AESEncryption; import dafny.DafnySequence; -import dafny.UByte; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.modes.GCMBlockCipher; import org.bouncycastle.crypto.params.AEADParameters; import org.bouncycastle.crypto.params.KeyParameter; -import static Utils.Util.bytesToUByteSequence; -import static Utils.Util.uByteSequenceToBytes; - //TODO This code has yet to be reviewed. See issue #36 public class AES_GCM { public static STL.Result AESEncrypt(EncryptionSuites.EncryptionSuite encAlg, - DafnySequence iv, - DafnySequence key, - DafnySequence msg, - DafnySequence aad) { + DafnySequence iv, + DafnySequence key, + DafnySequence msg, + DafnySequence aad) { try { GCMBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - AEADParameters param = new AEADParameters(new KeyParameter(uByteSequenceToBytes(key)), encAlg.tagLen.intValue() * 8, uByteSequenceToBytes(iv), uByteSequenceToBytes(aad)); + AEADParameters param = new AEADParameters(new KeyParameter(DafnySequence.toByteArray(key)), encAlg.tagLen * 8, DafnySequence.toByteArray(iv), DafnySequence.toByteArray(aad)); cipher.init(true, param); byte[] c = new byte[cipher.getOutputSize(msg.length())]; - int len = cipher.processBytes(uByteSequenceToBytes(msg), 0, msg.length(), c, 0); + int len = cipher.processBytes(DafnySequence.toByteArray(msg), 0, msg.length(), c, 0); cipher.doFinal(c, len); //Append authentication tag to `c` - return new STL.Result_Success(__default.EncryptionOutputFromByteSeq(bytesToUByteSequence(c), encAlg)); + return new STL.Result_Success(__default.EncryptionOutputFromByteSeq(DafnySequence.fromBytes(c), encAlg)); } catch (InvalidCipherTextException e) { return new STL.Result_Failure(DafnySequence.asString("aes encrypt err")); } } - public static STL.Result> AESDecrypt(EncryptionSuites.EncryptionSuite encAlg, DafnySequence key, DafnySequence cipherText, DafnySequence authTag, DafnySequence iv, DafnySequence aad) { + public static STL.Result> AESDecrypt(EncryptionSuites.EncryptionSuite encAlg, DafnySequence key, DafnySequence cipherText, DafnySequence authTag, DafnySequence iv, DafnySequence aad) { try { GCMBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - AEADParameters param = new AEADParameters(new KeyParameter(uByteSequenceToBytes(key)), encAlg.tagLen.intValue() * 8, uByteSequenceToBytes(iv), uByteSequenceToBytes(aad)); + AEADParameters param = new AEADParameters(new KeyParameter(DafnySequence.toByteArray(key)), encAlg.tagLen * 8, DafnySequence.toByteArray(iv), DafnySequence.toByteArray(aad)); cipher.init(false, param); - DafnySequence ctx = cipherText.concatenate(authTag); + DafnySequence ctx = cipherText.concatenate(authTag); byte[] pt = new byte[cipher.getOutputSize(ctx.length())]; - int len = cipher.processBytes(uByteSequenceToBytes(ctx), 0, ctx.length(), pt, 0); + int len = cipher.processBytes(DafnySequence.toByteArray(ctx), 0, ctx.length(), pt, 0); cipher.doFinal(pt, len); //Check message authentication tag - return new STL.Result_Success>(bytesToUByteSequence(pt)); + return new STL.Result_Success>(DafnySequence.fromBytes(pt)); } catch (InvalidCipherTextException macEx) { - return new STL.Result_Failure>(DafnySequence.asString(macEx.toString())); + return new STL.Result_Failure>(DafnySequence.asString(macEx.toString())); } catch (Exception e) { - return new STL.Result_Failure>(DafnySequence.asString("aes decrypt err")); + return new STL.Result_Failure>(DafnySequence.asString("aes decrypt err")); } } } diff --git a/src/extern/java/Arrays/Array.java b/src/extern/java/Arrays/Array.java index c090f1627..b44ba8117 100644 --- a/src/extern/java/Arrays/Array.java +++ b/src/extern/java/Arrays/Array.java @@ -1,22 +1,24 @@ package Arrays; +import dafny.Type; + import java.math.BigInteger; public class Array { private Array() { } - public static T[] copy(String td, T[] source, BigInteger length) { - if (length.intValue() == source.length) { - return source.clone(); + public static Object copy(Type t, Object source, BigInteger length) { + assert t.arrayType().isInstance(source); + + if (length.intValue() == t.getArrayLength(source)) { + return t.cloneArray(source); } else { - @SuppressWarnings("unchecked") - T[] dest = (T[]) java.lang.reflect.Array.newInstance( - source.getClass().getComponentType(), length.intValue()); - System.arraycopy(source, 0, dest, 0, length.intValue()); + Object dest = t.newArray(length.intValue()); + t.copyArrayTo(source, 0, dest, 0, length.intValue()); return dest; } } - public static void copyTo(T[] source, T[] dest, BigInteger offset) { - System.arraycopy(source, 0, dest, offset.intValue(), source.length); + public static void copyTo(Type t, Object source, Object dest, BigInteger offset) { + t.copyArrayTo(source, 0, dest, offset.intValue(), t.getArrayLength(source)); } } diff --git a/src/extern/java/BouncyCastleCryptoMac/HMac.java b/src/extern/java/BouncyCastleCryptoMac/HMac.java index c4d50cb23..57ce37bcf 100644 --- a/src/extern/java/BouncyCastleCryptoMac/HMac.java +++ b/src/extern/java/BouncyCastleCryptoMac/HMac.java @@ -1,11 +1,9 @@ package BouncyCastleCryptoMac; -import BouncyCastleCryptoMac.CipherParameters; import Digests.HMAC_ALGORITHM; import Utils.AlgorithmNotSupportedException; import Utils.Util; import dafny.DafnySequence; -import dafny.UByte; import java.math.BigInteger; @@ -39,7 +37,7 @@ public BigInteger getMacSize() { @Override public void init(CipherParameters ps) { if(ps.is_KeyParameter()) { - org.bouncycastle.crypto.params.KeyParameter bcKeyParameter = new org.bouncycastle.crypto.params.KeyParameter(Util.uBytesToBytes(ps.key)); + org.bouncycastle.crypto.params.KeyParameter bcKeyParameter = new org.bouncycastle.crypto.params.KeyParameter(ps.key); bcHMac.init(bcKeyParameter); } } @@ -50,20 +48,20 @@ public void reset() { } @Override - public void updateSingle(UByte input) { - bcHMac.update(input.byteValue()); + public void updateSingle(byte input) { + bcHMac.update(input); } @Override - public void update(UByte[] input , BigInteger inOff, BigInteger len) { - bcHMac.update(Util.uBytesToBytes(input), Util.bigIntegerToInt(inOff), Util.bigIntegerToInt(len)); + public void update(byte[] input , BigInteger inOff, BigInteger len) { + bcHMac.update(input, Util.bigIntegerToInt(inOff), Util.bigIntegerToInt(len)); } @Override - public BigInteger doFinal(UByte[] output, BigInteger outOff) { + public BigInteger doFinal(byte[] output, BigInteger outOff) { byte[] bytes = new byte[output.length - Util.bigIntegerToInt(outOff)]; BigInteger ans = BigInteger.valueOf(bcHMac.doFinal(bytes, 0)); - System.arraycopy(Util.bytesToUBytes(bytes), 0, output, outOff.intValue(), bytes.length); + System.arraycopy(bytes, 0, output, outOff.intValue(), bytes.length); return ans; } diff --git a/src/extern/java/RSAEncryption/RSA.java b/src/extern/java/RSAEncryption/RSA.java index dce261e6d..30fc75cf0 100644 --- a/src/extern/java/RSAEncryption/RSA.java +++ b/src/extern/java/RSAEncryption/RSA.java @@ -3,7 +3,6 @@ import Utils.BouncyCastleUtils; import dafny.DafnySequence; import dafny.Tuple2; -import dafny.UByte; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.openssl.PEMWriter; @@ -23,7 +22,7 @@ import static Utils.Util.*; public class RSA { - public static Tuple2 get_pem(KeyPair kp) { + public static Tuple2 get_pem(KeyPair kp) { try { byte[] pk; { @@ -42,8 +41,8 @@ public static Tuple2 get_pem(KeyPair kp) { pemWriter.flush(); sk = stringWriter.toString().getBytes(StandardCharsets.UTF_8); } - - return new Tuple2<>(bytesToUBytes(pk), bytesToUBytes(sk)); + + return new Tuple2<>(pk, sk); } catch (IOException e) { throw new RuntimeException(e); } @@ -55,7 +54,7 @@ public static Tuple2 get_pem(KeyPair kp) { @SuppressWarnings("unused") public static final int RSA_CERTAINTY = 256; - public static Tuple2, DafnySequence> GenerateKeyPairExtern(int bits, PaddingMode padding) { + public static Tuple2, DafnySequence> GenerateKeyPairExtern(int bits, PaddingMode padding) { KeyPairGenerator gen; try { gen = KeyPairGenerator.getInstance("RSA", BouncyCastleUtils.getProvider()); @@ -69,21 +68,21 @@ public static Tuple2, DafnySequence> GenerateKeyPair } KeyPair kp = gen.generateKeyPair(); - Tuple2 pair = get_pem(kp); - return new Tuple2<>(DafnySequence.fromArray(pair.dtor__0()), DafnySequence.fromArray(pair.dtor__1())); + Tuple2 pair = get_pem(kp); + return new Tuple2<>(DafnySequence.fromBytes(pair.dtor__0()), DafnySequence.fromBytes(pair.dtor__1())); } - public static STL.Result> EncryptExtern(PaddingMode padding, DafnySequence ek, DafnySequence msg) { + public static STL.Result> EncryptExtern(PaddingMode padding, DafnySequence ek, DafnySequence msg) { try { java.security.PublicKey pub; - PEMReader pemReader = new PEMReader(new StringReader(uByteSequenceToString(ek))); + PEMReader pemReader = new PEMReader(new StringReader(byteSequenceToString(ek))); Object pemObject = pemReader.readObject(); pub = ((java.security.PublicKey)pemObject); Cipher engine = createEngine(padding); engine.init(Cipher.ENCRYPT_MODE, pub); - return new STL.Result_Success<>(bytesToUByteSequence(engine.doFinal(uByteSequenceToBytes(msg)))); + return new STL.Result_Success<>(DafnySequence.fromBytes(engine.doFinal(DafnySequence.toByteArray(msg)))); } catch (IOException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e){ return new STL.Result_Failure<>(DafnySequence.asString("rsa encrypt error")); @@ -91,16 +90,16 @@ public static STL.Result> EncryptExtern(PaddingMode padding } - public static STL.Result> DecryptExtern(PaddingMode padding, DafnySequence dk, DafnySequence ctx) { + public static STL.Result> DecryptExtern(PaddingMode padding, DafnySequence dk, DafnySequence ctx) { try { KeyPair keyPair; Cipher engine = createEngine(padding); - Reader txtreader = new StringReader(uByteSequenceToString(dk)); + Reader txtreader = new StringReader(byteSequenceToString(dk)); keyPair = (KeyPair) new PEMReader(txtreader).readObject(); engine.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); - return new STL.Result_Success<>(bytesToUByteSequence(engine.doFinal(uByteSequenceToBytes(ctx)))); + return new STL.Result_Success<>(DafnySequence.fromBytes(engine.doFinal(DafnySequence.toByteArray(ctx)))); } catch (IOException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e){ return new STL.Result_Failure<>(DafnySequence.asString("rsa decrypt error")); diff --git a/src/extern/java/Random/__default.java b/src/extern/java/Random/__default.java index d1318f46e..a46726fbf 100644 --- a/src/extern/java/Random/__default.java +++ b/src/extern/java/Random/__default.java @@ -1,16 +1,14 @@ package Random; -import Utils.Util; import dafny.DafnySequence; -import dafny.UByte; import java.util.Random; public class __default { - public static DafnySequence GenerateBytes(int i) { + public static DafnySequence GenerateBytes(int i) { Random rng = new Random(); byte[] z = new byte[i]; rng.nextBytes(z); - return Util.bytesToUByteSequence(z); + return DafnySequence.fromBytes(z); } } diff --git a/src/extern/java/Signature/ECDSA.java b/src/extern/java/Signature/ECDSA.java index 194b2bf08..66778d1a9 100644 --- a/src/extern/java/Signature/ECDSA.java +++ b/src/extern/java/Signature/ECDSA.java @@ -1,9 +1,7 @@ package Signature; -import Utils.Util; import dafny.DafnySequence; import dafny.Tuple2; -import dafny.UByte; import org.bouncycastle.asn1.*; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.generators.ECKeyPairGenerator; @@ -22,11 +20,8 @@ import java.security.MessageDigest; import java.security.SecureRandom; -import static Utils.Util.bytesToUByteSequence; -import static Utils.Util.uByteSequenceToBytes; - public class ECDSA { - public static STL.Option, DafnySequence>> KeyGen(ECDSAParams x) { + public static STL.Option, DafnySequence>> KeyGen(ECDSAParams x) { try { ECKeyPairGenerator g = new ECKeyPairGenerator(); SecureRandom rng = new SecureRandom(); @@ -40,15 +35,15 @@ public static STL.Option, DafnySequence>> Key } AsymmetricCipherKeyPair kp = g.generateKeyPair(); ECPoint pt = ((ECPublicKeyParameters)kp.getPublic()).getQ(); - DafnySequence vk = bytesToUByteSequence(pt.getEncoded()); - DafnySequence sk = bytesToUByteSequence(((ECPrivateKeyParameters)kp.getPrivate()).getD().toByteArray()); + DafnySequence vk = DafnySequence.fromBytes(pt.getEncoded()); + DafnySequence sk = DafnySequence.fromBytes(((ECPrivateKeyParameters) kp.getPrivate()).getD().toByteArray()); return new STL.Option_Some<>(new Tuple2<>(vk, sk)); } catch (RuntimeException e) { return new STL.Option_None<>(); } } - public static boolean Verify(ECDSAParams x, DafnySequence vk, DafnySequence digest, DafnySequence sig) { + public static boolean Verify(ECDSAParams x, DafnySequence vk, DafnySequence digest, DafnySequence sig) { try { ECNamedCurveParameterSpec p; if (x.is_ECDSA__P384()) { @@ -57,19 +52,19 @@ public static boolean Verify(ECDSAParams x, DafnySequence vk, DafnySequen p = ECNamedCurveTable.getParameterSpec("secp256r1"); } ECDomainParameters dp = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()); - ECPoint pt = p.getCurve().decodePoint(uByteSequenceToBytes(vk)); + ECPoint pt = p.getCurve().decodePoint(DafnySequence.toByteArray(vk)); ECPublicKeyParameters vkp = new ECPublicKeyParameters(pt, dp); ECDSASigner sign = new ECDSASigner(); sign.init(false, vkp); BigInteger r, s; - Tuple2 pair = DERDeserialize(uByteSequenceToBytes(sig)); - return sign.verifySignature(uByteSequenceToBytes(digest), pair.dtor__0(), pair.dtor__1()); + Tuple2 pair = DERDeserialize(DafnySequence.toByteArray(sig)); + return sign.verifySignature(DafnySequence.toByteArray(digest), pair.dtor__0(), pair.dtor__1()); } catch (Exception e) { return false; } } - public static STL.Option> Sign(ECDSAParams x, DafnySequence sk, DafnySequence digest) { + public static STL.Option> Sign(ECDSAParams x, DafnySequence sk, DafnySequence digest) { try { ECNamedCurveParameterSpec p; if (x.is_ECDSA__P384()) { @@ -78,21 +73,21 @@ public static STL.Option> Sign(ECDSAParams x, DafnySequence p = ECNamedCurveTable.getParameterSpec("secp256r1"); } ECDomainParameters dp = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()); - ECPrivateKeyParameters skp = new ECPrivateKeyParameters(new BigInteger(uByteSequenceToBytes(sk)), dp); + ECPrivateKeyParameters skp = new ECPrivateKeyParameters(new BigInteger(DafnySequence.toByteArray(sk)), dp); ECDSASigner sign = new ECDSASigner(); sign.init(true, skp); do { - BigInteger[] sig = sign.generateSignature(uByteSequenceToBytes(digest)); + BigInteger[] sig = sign.generateSignature(DafnySequence.toByteArray(digest)); byte[] bytes = DERSerialize(sig[0], sig[1]); - if (bytes.length != x.SignatureLength().intValue()) { + if (bytes.length != x.SignatureLength()) { // Most of the time, a signature of the wrong length can be fixed // by negating s in the signature relative to the group order. bytes = DERSerialize(sig[0], p.getN().subtract(sig[1])); } - if (bytes.length == x.SignatureLength().intValue()) { + if (bytes.length == x.SignatureLength()) { // This will meet the method postcondition, which says that a Some? return must // contain a sequence of bytes whose length is x.SignatureLength(). - return new STL.Option_Some<>(bytesToUByteSequence(bytes)); + return new STL.Option_Some<>(DafnySequence.fromBytes(bytes)); } // We only get here with low probability, so try again (forever, if we have really bad luck). } while (true); @@ -101,15 +96,15 @@ public static STL.Option> Sign(ECDSAParams x, DafnySequence } } - public static DafnySequence Digest(ECDSAParams x, DafnySequence msg) { + public static DafnySequence Digest(ECDSAParams x, DafnySequence msg) { MessageDigest alg; if (x.is_ECDSA__P384()) { alg = new JDKMessageDigest.SHA384(); } else { alg = new JDKMessageDigest.SHA256(); } - byte[] digest = alg.digest(uByteSequenceToBytes(msg)); - return bytesToUByteSequence(digest); + byte[] digest = alg.digest(DafnySequence.toByteArray(msg)); + return DafnySequence.fromBytes(digest); } private static byte[] DERSerialize(BigInteger r, BigInteger s) { diff --git a/src/extern/java/UTF8/__default.java b/src/extern/java/UTF8/__default.java index 5e33265f3..eb588d580 100644 --- a/src/extern/java/UTF8/__default.java +++ b/src/extern/java/UTF8/__default.java @@ -1,15 +1,13 @@ package UTF8; -import Utils.Util; import dafny.DafnySequence; -import dafny.UByte; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.*; public class __default extends _ExternBase___default { - public static STL.Result> Encode(DafnySequence str) { + public static STL.Result> Encode(DafnySequence str) { CharsetEncoder utf8 = StandardCharsets.UTF_8.newEncoder(); utf8.onMalformedInput(CodingErrorAction.REPORT); utf8.onUnmappableCharacter(CodingErrorAction.REPORT); @@ -17,18 +15,18 @@ public static STL.Result> Encode(DafnySequence s ByteBuffer utf8Buf = utf8.encode(CharBuffer.wrap(str.verbatimString())); byte[] utf8Bytes = new byte[utf8Buf.limit()]; utf8Buf.get(utf8Bytes); - return new STL.Result_Success<>(Util.bytesToUByteSequence(utf8Bytes)); + return new STL.Result_Success<>(DafnySequence.fromBytes(utf8Bytes)); } catch (CharacterCodingException e) { return new STL.Result_Failure<>(DafnySequence.asString("Input contains invalid Unicode characters")); } } - public static STL.Result> Decode(DafnySequence bytes) { + public static STL.Result> Decode(DafnySequence bytes) { CharsetDecoder utf8 = StandardCharsets.UTF_8.newDecoder(); utf8.onMalformedInput(CodingErrorAction.REPORT); utf8.onUnmappableCharacter(CodingErrorAction.REPORT); try { - String decoded = utf8.decode(ByteBuffer.wrap(Util.uByteSequenceToBytes(bytes))).toString(); + String decoded = utf8.decode(ByteBuffer.wrap(DafnySequence.toByteArray(bytes))).toString(); return new STL.Result_Success<>(DafnySequence.asString(decoded)); } catch (CharacterCodingException e) { return new STL.Result_Failure<>(DafnySequence.asString("Input contains an invalid Unicode code point")); diff --git a/src/extern/java/Utils/Util.java b/src/extern/java/Utils/Util.java index 2bee54990..fe200747f 100644 --- a/src/extern/java/Utils/Util.java +++ b/src/extern/java/Utils/Util.java @@ -1,7 +1,6 @@ package Utils; import dafny.DafnySequence; -import dafny.UByte; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -19,43 +18,7 @@ public static int bigIntegerToInt(BigInteger x) { } } - public static UByte[] bytesToUBytes(byte[] bytes) { - int len = bytes.length; - UByte[] ans = new UByte[len]; - for (int i = 0; i < len; i++) { - ans[i] = new UByte(bytes[i]); - } - return ans; - } - - public static byte[] uBytesToBytes(UByte[] uBytes) { - int len = uBytes.length; - byte[] ans = new byte[len]; - for (int i = 0; i < len; i++) { - ans[i] = uBytes[i].byteValue(); - } - return ans; - } - - public static DafnySequence bytesToUByteSequence(byte[] bytes) { - return DafnySequence.fromArray(bytesToUBytes(bytes)); - } - - public static byte[] uByteSequenceToBytes(DafnySequence seq) { - int len = seq.length(); - byte[] ans = new byte[len]; - int i = 0; - for (UByte b : seq) { - ans[i++] = b.byteValue(); - } - return ans; - } - - public static DafnySequence stringToUByteSequence(String string) { - return bytesToUByteSequence(string.getBytes(StandardCharsets.UTF_8)); - } - - public static String uByteSequenceToString(DafnySequence uBytes) { - return new String(uByteSequenceToBytes(uBytes), StandardCharsets.UTF_8); + public static String byteSequenceToString(DafnySequence bytes) { + return new String(DafnySequence.toByteArray(bytes), StandardCharsets.UTF_8); } } From 2f18397704de5cf8f69a52ac38bf06da1ce14b68 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sat, 23 May 2020 08:46:44 -0700 Subject: [PATCH 05/10] Extracting Java code to separate repo See https://github.com/awslabs/aws-encryption-sdk-java-formally-verified --- bench/extern/java/Bench/__default.java | 4 - build.gradle | 127 ------------ gradle/wrapper/gradle-wrapper.jar | Bin 58702 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 6 - gradlew | 183 ------------------ gradlew.bat | 100 ---------- lib/.gitignore | 5 - settings.gradle | 1 - src/extern/java/AESEncryption/AES_GCM.java | 53 ----- src/extern/java/AESEncryption/__default.java | 4 - src/extern/java/Arrays/Array.java | 22 --- .../java/BouncyCastleCryptoMac/HMac.java | 75 ------- src/extern/java/ESDKClient/__default.java | 4 - .../java/EncryptionSuites/__default.java | 4 - src/extern/java/Materials/__default.java | 4 - src/extern/java/RSAEncryption/RSA.java | 127 ------------ src/extern/java/RSAEncryption/__default.java | 4 - src/extern/java/Random/__default.java | 16 -- src/extern/java/STL/__default.java | 4 - src/extern/java/STLUInt/__default.java | 4 - src/extern/java/Signature/ECDSA.java | 138 ------------- src/extern/java/Signature/__default.java | 4 - src/extern/java/Time/Timer.java | 15 -- src/extern/java/UTF8/__default.java | 37 ---- .../Utils/AlgorithmNotSupportedException.java | 7 - src/extern/java/Utils/BouncyCastleUtils.java | 20 -- src/extern/java/Utils/Util.java | 61 ------ test/extern/java/TestClient/__default.java | 4 - 28 files changed, 1033 deletions(-) delete mode 100644 bench/extern/java/Bench/__default.java delete mode 100644 build.gradle delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat delete mode 100644 lib/.gitignore delete mode 100644 settings.gradle delete mode 100644 src/extern/java/AESEncryption/AES_GCM.java delete mode 100644 src/extern/java/AESEncryption/__default.java delete mode 100644 src/extern/java/Arrays/Array.java delete mode 100644 src/extern/java/BouncyCastleCryptoMac/HMac.java delete mode 100644 src/extern/java/ESDKClient/__default.java delete mode 100644 src/extern/java/EncryptionSuites/__default.java delete mode 100644 src/extern/java/Materials/__default.java delete mode 100644 src/extern/java/RSAEncryption/RSA.java delete mode 100644 src/extern/java/RSAEncryption/__default.java delete mode 100644 src/extern/java/Random/__default.java delete mode 100644 src/extern/java/STL/__default.java delete mode 100644 src/extern/java/STLUInt/__default.java delete mode 100644 src/extern/java/Signature/ECDSA.java delete mode 100644 src/extern/java/Signature/__default.java delete mode 100644 src/extern/java/Time/Timer.java delete mode 100644 src/extern/java/UTF8/__default.java delete mode 100644 src/extern/java/Utils/AlgorithmNotSupportedException.java delete mode 100644 src/extern/java/Utils/BouncyCastleUtils.java delete mode 100644 src/extern/java/Utils/Util.java delete mode 100644 test/extern/java/TestClient/__default.java diff --git a/bench/extern/java/Bench/__default.java b/bench/extern/java/Bench/__default.java deleted file mode 100644 index b73ad6c25..000000000 --- a/bench/extern/java/Bench/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package Bench; - -public class __default extends _ExternBase___default { -} diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 5213e0084..000000000 --- a/build.gradle +++ /dev/null @@ -1,127 +0,0 @@ -// TODO: This build awkwardly generates all Java sources twice, once for the -// library and once for the benchmark suite, where just one .dfy file differs -// between the two runs. It works, but there's probably a better way. - -plugins { - id 'java' - id 'maven-publish' -} - -repositories { - mavenLocal() - mavenCentral() -} - -group = 'com.example' -version = '1.0-SNAPSHOT' -sourceCompatibility = '1.8' - -project.buildDir = 'build/java' - -sourceSets { - main { - java { - srcDirs = ['src/extern/java', 'test/extern/java', 'build/java/src/main'] - } - } - bench { - java { - srcDirs = ['src/extern/java', 'bench/extern/java', 'build/java/src/bench'] - } - } -} - -dependencies { - implementation 'org.bouncycastle:bcprov-jdk16:1.46' - implementation 'dafny.lang:DafnyRuntime:1' - - benchImplementation 'org.bouncycastle:bcprov-jdk16:1.46' - benchImplementation 'dafny.lang:DafnyRuntime:1' -} - - -def libSrcs = [ - 'src/Crypto/AESEncryption.dfy', - 'src/Crypto/EncryptionSuites.dfy', - 'src/Crypto/Digests.dfy', - 'src/Crypto/HKDF/CryptoMac.dfy', - 'src/Crypto/HKDF/HKDF.dfy', - 'src/Crypto/HKDF/HKDFSpec.dfy', - 'src/Crypto/Random.dfy', - 'src/Crypto/RSAEncryption.dfy', - 'src/Crypto/Signature.dfy', - 'src/SDK/AlgorithmSuite.dfy', - 'src/SDK/Client.dfy', - 'src/SDK/CMM/DefaultCMM.dfy', - 'src/SDK/CMM/Defs.dfy', - 'src/SDK/Deserialize.dfy', - 'src/SDK/Keyring/RawAESKeyring.dfy', - 'src/SDK/Keyring/Defs.dfy', - 'src/SDK/Keyring/MultiKeyring.dfy', - 'src/SDK/Keyring/RawRSAKeyring.dfy', - 'src/SDK/Materials.dfy', - 'src/SDK/MessageBody.dfy', - 'src/SDK/MessageHeader.dfy', - 'src/SDK/Serialize.dfy', - 'src/SDK/ToyClient.dfy', - 'src/StandardLibrary/Base64.dfy', - 'src/StandardLibrary/StandardLibrary.dfy', - 'src/StandardLibrary/UInt.dfy', - 'src/Util/Arrays.dfy', - 'src/Util/Streams.dfy', - 'src/Util/UTF8.dfy', -] - -def dafnyOpts = [ - '/compile:0', - '/noVerify', - '/noIncludes', - '/spillTargetCode:3', - '/compileTarget:java', -] - -// TODO: Make a custom task type to avoid duplication. - -task genSources(type: Exec) { - executable = 'dafny' - args "/out:build/java/src/main" // Must be relative path (Dafny bug?) - args libSrcs - args 'test/SDK/Client.dfy' - args dafnyOpts - inputs.files(libSrcs) - inputs.file('test/SDK/Client.dfy') - outputs.dir("$buildDir/src/main") -} - -task genBenchSources(type: Exec) { - executable = 'dafny' - args "/out:build/java/src/bench" // Must be relative path (Dafny bug?) - args libSrcs - args 'bench/SDK/Bench.dfy' - args dafnyOpts - inputs.files(libSrcs) - inputs.file('bench/SDK/Bench.dfy') - outputs.dir("$buildDir/src/bench") -} - -gradle.projectsEvaluated { - compileJava.dependsOn genSources - compileBenchJava.dependsOn genBenchSources -} - -publishing { - publications { - maven(MavenPublication) { - from(components.java) - } - } -} - -tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' -} - -task bench(type: JavaExec) { - classpath = sourceSets.bench.runtimeClasspath - main = 'bench' -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58702 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

@up^y4&I8N>~wvL%f(S2W%NN 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 D613UA diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 8395f0741..000000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Tue Dec 17 18:31:06 PST 2019 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-all.zip -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStorePath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew deleted file mode 100755 index 2fe81a7d9..000000000 --- a/gradlew +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 9618d8d96..000000000 --- a/gradlew.bat +++ /dev/null @@ -1,100 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/lib/.gitignore b/lib/.gitignore deleted file mode 100644 index 05687e878..000000000 --- a/lib/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# ignore all files in this dir... -* - -# ... except for this one. -!.gitignore diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 951c02c01..000000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'aws-encryption-sdk-dafny' diff --git a/src/extern/java/AESEncryption/AES_GCM.java b/src/extern/java/AESEncryption/AES_GCM.java deleted file mode 100644 index a25754e0a..000000000 --- a/src/extern/java/AESEncryption/AES_GCM.java +++ /dev/null @@ -1,53 +0,0 @@ -package AESEncryption; - -import dafny.DafnySequence; -import dafny.UByte; -import org.bouncycastle.crypto.InvalidCipherTextException; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - -import static Utils.Util.bytesToUByteSequence; -import static Utils.Util.uByteSequenceToBytes; - -//TODO This code has yet to be reviewed. See issue #36 -public class AES_GCM { - - public static STL.Result AESEncrypt(EncryptionSuites.EncryptionSuite encAlg, - DafnySequence iv, - DafnySequence key, - DafnySequence msg, - DafnySequence aad) { - try { - GCMBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - AEADParameters param = new AEADParameters(new KeyParameter(uByteSequenceToBytes(key)), encAlg.tagLen.intValue() * 8, uByteSequenceToBytes(iv), uByteSequenceToBytes(aad)); - cipher.init(true, param); - - byte[] c = new byte[cipher.getOutputSize(msg.length())]; - int len = cipher.processBytes(uByteSequenceToBytes(msg), 0, msg.length(), c, 0); - cipher.doFinal(c, len); //Append authentication tag to `c` - return new STL.Result_Success(__default.EncryptionOutputFromByteSeq(bytesToUByteSequence(c), encAlg)); - } - catch (InvalidCipherTextException e) { - return new STL.Result_Failure(DafnySequence.asString("aes encrypt err")); - } - } - - public static STL.Result> AESDecrypt(EncryptionSuites.EncryptionSuite encAlg, DafnySequence key, DafnySequence cipherText, DafnySequence authTag, DafnySequence iv, DafnySequence aad) { - try { - GCMBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - AEADParameters param = new AEADParameters(new KeyParameter(uByteSequenceToBytes(key)), encAlg.tagLen.intValue() * 8, uByteSequenceToBytes(iv), uByteSequenceToBytes(aad)); - cipher.init(false, param); - DafnySequence ctx = cipherText.concatenate(authTag); - byte[] pt = new byte[cipher.getOutputSize(ctx.length())]; - int len = cipher.processBytes(uByteSequenceToBytes(ctx), 0, ctx.length(), pt, 0); - cipher.doFinal(pt, len); //Check message authentication tag - return new STL.Result_Success>(bytesToUByteSequence(pt)); - } catch (InvalidCipherTextException macEx) { - return new STL.Result_Failure>(DafnySequence.asString(macEx.toString())); - } catch (Exception e) { - return new STL.Result_Failure>(DafnySequence.asString("aes decrypt err")); - } - } -} diff --git a/src/extern/java/AESEncryption/__default.java b/src/extern/java/AESEncryption/__default.java deleted file mode 100644 index be5469165..000000000 --- a/src/extern/java/AESEncryption/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package AESEncryption; - -public class __default extends _ExternBase___default { -} diff --git a/src/extern/java/Arrays/Array.java b/src/extern/java/Arrays/Array.java deleted file mode 100644 index c090f1627..000000000 --- a/src/extern/java/Arrays/Array.java +++ /dev/null @@ -1,22 +0,0 @@ -package Arrays; - -import java.math.BigInteger; - -public class Array { - private Array() { } - - public static T[] copy(String td, T[] source, BigInteger length) { - if (length.intValue() == source.length) { - return source.clone(); - } else { - @SuppressWarnings("unchecked") - T[] dest = (T[]) java.lang.reflect.Array.newInstance( - source.getClass().getComponentType(), length.intValue()); - System.arraycopy(source, 0, dest, 0, length.intValue()); - return dest; - } - } - public static void copyTo(T[] source, T[] dest, BigInteger offset) { - System.arraycopy(source, 0, dest, offset.intValue(), source.length); - } -} diff --git a/src/extern/java/BouncyCastleCryptoMac/HMac.java b/src/extern/java/BouncyCastleCryptoMac/HMac.java deleted file mode 100644 index c4d50cb23..000000000 --- a/src/extern/java/BouncyCastleCryptoMac/HMac.java +++ /dev/null @@ -1,75 +0,0 @@ -package BouncyCastleCryptoMac; - -import BouncyCastleCryptoMac.CipherParameters; -import Digests.HMAC_ALGORITHM; -import Utils.AlgorithmNotSupportedException; -import Utils.Util; -import dafny.DafnySequence; -import dafny.UByte; - -import java.math.BigInteger; - -public class HMac extends _ExternBase_HMac { - - private org.bouncycastle.crypto.macs.HMac bcHMac; - - public HMac(Digests.HMAC_ALGORITHM algorithm) { - org.bouncycastle.crypto.Digest digest; - if(algorithm.is_HmacSHA256()) { - digest = new org.bouncycastle.crypto.digests.SHA256Digest(); - bcHMac = new org.bouncycastle.crypto.macs.HMac(digest); - } else if(algorithm.is_HmacSHA384()) { - digest = new org.bouncycastle.crypto.digests.SHA384Digest(); - bcHMac = new org.bouncycastle.crypto.macs.HMac(digest); - } else { - throw new AlgorithmNotSupportedException(algorithm.toString() + " not supported."); - } - } - - @Override - public DafnySequence getAlgorithmName() { - return DafnySequence.asString(bcHMac.getAlgorithmName()); - } - - @Override - public BigInteger getMacSize() { - return BigInteger.valueOf(bcHMac.getMacSize()); - } - - @Override - public void init(CipherParameters ps) { - if(ps.is_KeyParameter()) { - org.bouncycastle.crypto.params.KeyParameter bcKeyParameter = new org.bouncycastle.crypto.params.KeyParameter(Util.uBytesToBytes(ps.key)); - bcHMac.init(bcKeyParameter); - } - } - - @Override - public void reset() { - bcHMac.reset(); - } - - @Override - public void updateSingle(UByte input) { - bcHMac.update(input.byteValue()); - } - - @Override - public void update(UByte[] input , BigInteger inOff, BigInteger len) { - bcHMac.update(Util.uBytesToBytes(input), Util.bigIntegerToInt(inOff), Util.bigIntegerToInt(len)); - } - - @Override - public BigInteger doFinal(UByte[] output, BigInteger outOff) { - byte[] bytes = new byte[output.length - Util.bigIntegerToInt(outOff)]; - BigInteger ans = BigInteger.valueOf(bcHMac.doFinal(bytes, 0)); - System.arraycopy(Util.bytesToUBytes(bytes), 0, output, outOff.intValue(), bytes.length); - return ans; - } - - @Override - public HMAC_ALGORITHM getUnderlyingDigest() { - // TODO - throw new UnsupportedOperationException(); - } -} diff --git a/src/extern/java/ESDKClient/__default.java b/src/extern/java/ESDKClient/__default.java deleted file mode 100644 index 7b8cc4615..000000000 --- a/src/extern/java/ESDKClient/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package ESDKClient; - -public class __default extends _ExternBase___default { -} diff --git a/src/extern/java/EncryptionSuites/__default.java b/src/extern/java/EncryptionSuites/__default.java deleted file mode 100644 index aec086106..000000000 --- a/src/extern/java/EncryptionSuites/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package EncryptionSuites; - -public class __default extends _ExternBase___default { -} diff --git a/src/extern/java/Materials/__default.java b/src/extern/java/Materials/__default.java deleted file mode 100644 index e965c870a..000000000 --- a/src/extern/java/Materials/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package Materials; - -public class __default extends _ExternBase___default { -} diff --git a/src/extern/java/RSAEncryption/RSA.java b/src/extern/java/RSAEncryption/RSA.java deleted file mode 100644 index dce261e6d..000000000 --- a/src/extern/java/RSAEncryption/RSA.java +++ /dev/null @@ -1,127 +0,0 @@ -package RSAEncryption; - -import Utils.BouncyCastleUtils; -import dafny.DafnySequence; -import dafny.Tuple2; -import dafny.UByte; -import org.bouncycastle.openssl.PEMReader; -import org.bouncycastle.openssl.PEMWriter; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.io.StringWriter; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.*; -import java.security.spec.RSAKeyGenParameterSpec; - -import static Utils.Util.*; - -public class RSA { - public static Tuple2 get_pem(KeyPair kp) { - try { - byte[] pk; - { - StringWriter stringWriter = new StringWriter(); - PEMWriter pemWriter = new PEMWriter(stringWriter); - pemWriter.writeObject(kp.getPublic()); - pemWriter.flush(); - pk = stringWriter.toString().getBytes(StandardCharsets.UTF_8); - } - - byte[] sk; - { - StringWriter stringWriter = new StringWriter(); - PEMWriter pemWriter = new PEMWriter(stringWriter); - pemWriter.writeObject(kp.getPrivate()); - pemWriter.flush(); - sk = stringWriter.toString().getBytes(StandardCharsets.UTF_8); - } - - return new Tuple2<>(bytesToUBytes(pk), bytesToUBytes(sk)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static final BigInteger RSA_PUBLIC_EXPONENT = BigInteger.valueOf(65537); - // XXX This parameter is passed using the Bouncy Castle API in the C# implementation, - // but there doesn't appear to be a way to pass it using JCE - @SuppressWarnings("unused") - public static final int RSA_CERTAINTY = 256; - - public static Tuple2, DafnySequence> GenerateKeyPairExtern(int bits, PaddingMode padding) { - KeyPairGenerator gen; - try { - gen = KeyPairGenerator.getInstance("RSA", BouncyCastleUtils.getProvider()); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - try { - gen.initialize(new RSAKeyGenParameterSpec(bits, RSA_PUBLIC_EXPONENT)); - } catch (InvalidAlgorithmParameterException e) { - throw new RuntimeException(e); - } - - KeyPair kp = gen.generateKeyPair(); - Tuple2 pair = get_pem(kp); - return new Tuple2<>(DafnySequence.fromArray(pair.dtor__0()), DafnySequence.fromArray(pair.dtor__1())); - } - - public static STL.Result> EncryptExtern(PaddingMode padding, DafnySequence ek, DafnySequence msg) { - try { - java.security.PublicKey pub; - PEMReader pemReader = new PEMReader(new StringReader(uByteSequenceToString(ek))); - Object pemObject = pemReader.readObject(); - pub = ((java.security.PublicKey)pemObject); - - Cipher engine = createEngine(padding); - - engine.init(Cipher.ENCRYPT_MODE, pub); - return new STL.Result_Success<>(bytesToUByteSequence(engine.doFinal(uByteSequenceToBytes(msg)))); - } - catch (IOException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e){ - return new STL.Result_Failure<>(DafnySequence.asString("rsa encrypt error")); - } - - } - - public static STL.Result> DecryptExtern(PaddingMode padding, DafnySequence dk, DafnySequence ctx) { - try { - KeyPair keyPair; - - Cipher engine = createEngine(padding); - - Reader txtreader = new StringReader(uByteSequenceToString(dk)); - keyPair = (KeyPair) new PEMReader(txtreader).readObject(); - engine.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); - return new STL.Result_Success<>(bytesToUByteSequence(engine.doFinal(uByteSequenceToBytes(ctx)))); - } - catch (IOException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e){ - return new STL.Result_Failure<>(DafnySequence.asString("rsa decrypt error")); - } - } - - public static Cipher createEngine(PaddingMode padding) { - String alg; - - if (padding.is_PKCS1()) { - alg = "RSA/ECB/PKCS1Padding"; - } else if (padding.is_OAEP__SHA1()) { - alg = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; - } else { // padding.is_OAEP__SHA256 - alg = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; - } - - try { - return Cipher.getInstance(alg, BouncyCastleUtils.getProvider()); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/extern/java/RSAEncryption/__default.java b/src/extern/java/RSAEncryption/__default.java deleted file mode 100644 index edbe38d10..000000000 --- a/src/extern/java/RSAEncryption/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package RSAEncryption; - -public class __default extends _ExternBase___default { -} diff --git a/src/extern/java/Random/__default.java b/src/extern/java/Random/__default.java deleted file mode 100644 index d1318f46e..000000000 --- a/src/extern/java/Random/__default.java +++ /dev/null @@ -1,16 +0,0 @@ -package Random; - -import Utils.Util; -import dafny.DafnySequence; -import dafny.UByte; - -import java.util.Random; - -public class __default { - public static DafnySequence GenerateBytes(int i) { - Random rng = new Random(); - byte[] z = new byte[i]; - rng.nextBytes(z); - return Util.bytesToUByteSequence(z); - } -} diff --git a/src/extern/java/STL/__default.java b/src/extern/java/STL/__default.java deleted file mode 100644 index 72e7b1707..000000000 --- a/src/extern/java/STL/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package STL; - -public class __default extends _ExternBase___default { -} diff --git a/src/extern/java/STLUInt/__default.java b/src/extern/java/STLUInt/__default.java deleted file mode 100644 index a5d67b1f7..000000000 --- a/src/extern/java/STLUInt/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package STLUInt; - -public class __default extends _ExternBase___default { -} diff --git a/src/extern/java/Signature/ECDSA.java b/src/extern/java/Signature/ECDSA.java deleted file mode 100644 index 194b2bf08..000000000 --- a/src/extern/java/Signature/ECDSA.java +++ /dev/null @@ -1,138 +0,0 @@ -package Signature; - -import Utils.Util; -import dafny.DafnySequence; -import dafny.Tuple2; -import dafny.UByte; -import org.bouncycastle.asn1.*; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; -import org.bouncycastle.crypto.generators.ECKeyPairGenerator; -import org.bouncycastle.crypto.params.ECDomainParameters; -import org.bouncycastle.crypto.params.ECKeyGenerationParameters; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.provider.JDKMessageDigest; -import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; -import org.bouncycastle.math.ec.ECPoint; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.SecureRandom; - -import static Utils.Util.bytesToUByteSequence; -import static Utils.Util.uByteSequenceToBytes; - -public class ECDSA { - public static STL.Option, DafnySequence>> KeyGen(ECDSAParams x) { - try { - ECKeyPairGenerator g = new ECKeyPairGenerator(); - SecureRandom rng = new SecureRandom(); - ECNamedCurveParameterSpec p; - if (x.is_ECDSA__P384()) { - p = ECNamedCurveTable.getParameterSpec("secp384r1"); - g.init(new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()), rng)); - } else { // x is ECDSA__P256 - p = ECNamedCurveTable.getParameterSpec("secp256r1"); - g.init(new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()), rng)); - } - AsymmetricCipherKeyPair kp = g.generateKeyPair(); - ECPoint pt = ((ECPublicKeyParameters)kp.getPublic()).getQ(); - DafnySequence vk = bytesToUByteSequence(pt.getEncoded()); - DafnySequence sk = bytesToUByteSequence(((ECPrivateKeyParameters)kp.getPrivate()).getD().toByteArray()); - return new STL.Option_Some<>(new Tuple2<>(vk, sk)); - } catch (RuntimeException e) { - return new STL.Option_None<>(); - } - } - - public static boolean Verify(ECDSAParams x, DafnySequence vk, DafnySequence digest, DafnySequence sig) { - try { - ECNamedCurveParameterSpec p; - if (x.is_ECDSA__P384()) { - p = ECNamedCurveTable.getParameterSpec("secp384r1"); - } else { - p = ECNamedCurveTable.getParameterSpec("secp256r1"); - } - ECDomainParameters dp = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()); - ECPoint pt = p.getCurve().decodePoint(uByteSequenceToBytes(vk)); - ECPublicKeyParameters vkp = new ECPublicKeyParameters(pt, dp); - ECDSASigner sign = new ECDSASigner(); - sign.init(false, vkp); - BigInteger r, s; - Tuple2 pair = DERDeserialize(uByteSequenceToBytes(sig)); - return sign.verifySignature(uByteSequenceToBytes(digest), pair.dtor__0(), pair.dtor__1()); - } catch (Exception e) { - return false; - } - } - - public static STL.Option> Sign(ECDSAParams x, DafnySequence sk, DafnySequence digest) { - try { - ECNamedCurveParameterSpec p; - if (x.is_ECDSA__P384()) { - p = ECNamedCurveTable.getParameterSpec("secp384r1"); - } else { - p = ECNamedCurveTable.getParameterSpec("secp256r1"); - } - ECDomainParameters dp = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()); - ECPrivateKeyParameters skp = new ECPrivateKeyParameters(new BigInteger(uByteSequenceToBytes(sk)), dp); - ECDSASigner sign = new ECDSASigner(); - sign.init(true, skp); - do { - BigInteger[] sig = sign.generateSignature(uByteSequenceToBytes(digest)); - byte[] bytes = DERSerialize(sig[0], sig[1]); - if (bytes.length != x.SignatureLength().intValue()) { - // Most of the time, a signature of the wrong length can be fixed - // by negating s in the signature relative to the group order. - bytes = DERSerialize(sig[0], p.getN().subtract(sig[1])); - } - if (bytes.length == x.SignatureLength().intValue()) { - // This will meet the method postcondition, which says that a Some? return must - // contain a sequence of bytes whose length is x.SignatureLength(). - return new STL.Option_Some<>(bytesToUByteSequence(bytes)); - } - // We only get here with low probability, so try again (forever, if we have really bad luck). - } while (true); - } catch (RuntimeException e) { - return new STL.Option_None<>(); - } - } - - public static DafnySequence Digest(ECDSAParams x, DafnySequence msg) { - MessageDigest alg; - if (x.is_ECDSA__P384()) { - alg = new JDKMessageDigest.SHA384(); - } else { - alg = new JDKMessageDigest.SHA256(); - } - byte[] digest = alg.digest(uByteSequenceToBytes(msg)); - return bytesToUByteSequence(digest); - } - - private static byte[] DERSerialize(BigInteger r, BigInteger s) { - DERSequence derSeq = new DERSequence(new ASN1Encodable[] { new DERInteger(r), new DERInteger(s) }); - try { - return derSeq.getEncoded(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private static Tuple2 DERDeserialize(byte[] bytes) { - ASN1InputStream asn1 = new ASN1InputStream(bytes); - ASN1Sequence seq; - try { - seq = (ASN1Sequence) asn1.readObject(); - } catch (IOException e) { - throw new RuntimeException(e); - } - DERInteger dr = (DERInteger) seq.getObjectAt(0); - DERInteger ds = (DERInteger) seq.getObjectAt(1); - BigInteger r = new BigInteger(dr.toString()); - BigInteger s = new BigInteger(ds.toString()); - return new Tuple2<>(r, s); - } -} diff --git a/src/extern/java/Signature/__default.java b/src/extern/java/Signature/__default.java deleted file mode 100644 index ab3a7ab81..000000000 --- a/src/extern/java/Signature/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package Signature; - -public class __default { -} diff --git a/src/extern/java/Time/Timer.java b/src/extern/java/Time/Timer.java deleted file mode 100644 index 34f0e4b5f..000000000 --- a/src/extern/java/Time/Timer.java +++ /dev/null @@ -1,15 +0,0 @@ -package Time; - -import java.math.BigInteger; - -public class Timer { - private long start; - - public Timer() { - start = System.currentTimeMillis(); - } - - public BigInteger ElapsedMilliseconds() { - return BigInteger.valueOf(System.currentTimeMillis() - start); - } -} diff --git a/src/extern/java/UTF8/__default.java b/src/extern/java/UTF8/__default.java deleted file mode 100644 index 5e33265f3..000000000 --- a/src/extern/java/UTF8/__default.java +++ /dev/null @@ -1,37 +0,0 @@ -package UTF8; - -import Utils.Util; -import dafny.DafnySequence; -import dafny.UByte; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.*; - -public class __default extends _ExternBase___default { - public static STL.Result> Encode(DafnySequence str) { - CharsetEncoder utf8 = StandardCharsets.UTF_8.newEncoder(); - utf8.onMalformedInput(CodingErrorAction.REPORT); - utf8.onUnmappableCharacter(CodingErrorAction.REPORT); - try { - ByteBuffer utf8Buf = utf8.encode(CharBuffer.wrap(str.verbatimString())); - byte[] utf8Bytes = new byte[utf8Buf.limit()]; - utf8Buf.get(utf8Bytes); - return new STL.Result_Success<>(Util.bytesToUByteSequence(utf8Bytes)); - } catch (CharacterCodingException e) { - return new STL.Result_Failure<>(DafnySequence.asString("Input contains invalid Unicode characters")); - } - } - - public static STL.Result> Decode(DafnySequence bytes) { - CharsetDecoder utf8 = StandardCharsets.UTF_8.newDecoder(); - utf8.onMalformedInput(CodingErrorAction.REPORT); - utf8.onUnmappableCharacter(CodingErrorAction.REPORT); - try { - String decoded = utf8.decode(ByteBuffer.wrap(Util.uByteSequenceToBytes(bytes))).toString(); - return new STL.Result_Success<>(DafnySequence.asString(decoded)); - } catch (CharacterCodingException e) { - return new STL.Result_Failure<>(DafnySequence.asString("Input contains an invalid Unicode code point")); - } - } -} diff --git a/src/extern/java/Utils/AlgorithmNotSupportedException.java b/src/extern/java/Utils/AlgorithmNotSupportedException.java deleted file mode 100644 index 90c69313e..000000000 --- a/src/extern/java/Utils/AlgorithmNotSupportedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package Utils; - -public class AlgorithmNotSupportedException extends RuntimeException { - public AlgorithmNotSupportedException(String message) { - super(message); - } -} diff --git a/src/extern/java/Utils/BouncyCastleUtils.java b/src/extern/java/Utils/BouncyCastleUtils.java deleted file mode 100644 index c98d471e8..000000000 --- a/src/extern/java/Utils/BouncyCastleUtils.java +++ /dev/null @@ -1,20 +0,0 @@ -package Utils; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -import java.security.Security; - -public class BouncyCastleUtils { - private BouncyCastleUtils() { } - - private static final BouncyCastleProvider PROVIDER = - new BouncyCastleProvider(); - - static { - Security.addProvider(PROVIDER); - } - - public static BouncyCastleProvider getProvider() { - return PROVIDER; - } -} diff --git a/src/extern/java/Utils/Util.java b/src/extern/java/Utils/Util.java deleted file mode 100644 index 2bee54990..000000000 --- a/src/extern/java/Utils/Util.java +++ /dev/null @@ -1,61 +0,0 @@ -package Utils; - -import dafny.DafnySequence; -import dafny.UByte; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; - -public class Util { - private Util() { } - - public static int bigIntegerToInt(BigInteger x) { - try { - return x.intValueExact(); - } catch(ArithmeticException e) { - // TODO: error handling - System.out.println(e.toString()); - throw e; - } - } - - public static UByte[] bytesToUBytes(byte[] bytes) { - int len = bytes.length; - UByte[] ans = new UByte[len]; - for (int i = 0; i < len; i++) { - ans[i] = new UByte(bytes[i]); - } - return ans; - } - - public static byte[] uBytesToBytes(UByte[] uBytes) { - int len = uBytes.length; - byte[] ans = new byte[len]; - for (int i = 0; i < len; i++) { - ans[i] = uBytes[i].byteValue(); - } - return ans; - } - - public static DafnySequence bytesToUByteSequence(byte[] bytes) { - return DafnySequence.fromArray(bytesToUBytes(bytes)); - } - - public static byte[] uByteSequenceToBytes(DafnySequence seq) { - int len = seq.length(); - byte[] ans = new byte[len]; - int i = 0; - for (UByte b : seq) { - ans[i++] = b.byteValue(); - } - return ans; - } - - public static DafnySequence stringToUByteSequence(String string) { - return bytesToUByteSequence(string.getBytes(StandardCharsets.UTF_8)); - } - - public static String uByteSequenceToString(DafnySequence uBytes) { - return new String(uByteSequenceToBytes(uBytes), StandardCharsets.UTF_8); - } -} diff --git a/test/extern/java/TestClient/__default.java b/test/extern/java/TestClient/__default.java deleted file mode 100644 index 723b47f18..000000000 --- a/test/extern/java/TestClient/__default.java +++ /dev/null @@ -1,4 +0,0 @@ -package TestClient; - -public class __default extends _ExternBase___default { -} From d3693d2764c8dc104a0abdd52d654c36e0ed0b6d Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sun, 24 May 2020 08:44:24 -0700 Subject: [PATCH 06/10] Remove gradle filter in .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index a3df08642..321899403 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,6 @@ test/**/Output/* /generated-test /bench/bin /bench/obj -.gradle/* /testVectors/bin /testVectors/obj From 5456b02a24f5d533f72fd2f1c2e9b8bd005da499 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sun, 24 May 2020 08:52:23 -0700 Subject: [PATCH 07/10] Removing C# again (bad merge) --- buildspec.yml | 3 - nuget.config | 6 - src/api/dotnet/CMMs.cs | 18 -- src/api/dotnet/Client.cs | 49 ----- src/api/dotnet/DafnyFFI.cs | 62 ------- src/api/dotnet/Keyrings.cs | 29 --- src/extern/dotnet/AESEncryption.cs | 53 ------ src/extern/dotnet/Arrays-extern.cs | 16 -- src/extern/dotnet/HKDF-extern.cs | 84 --------- src/extern/dotnet/KMS.cs | 129 ------------- src/extern/dotnet/RSAEncryption.cs | 121 ------------ src/extern/dotnet/Random.cs | 13 -- src/extern/dotnet/Signature.cs | 121 ------------ src/extern/dotnet/Time.cs | 23 --- src/extern/dotnet/UTF8.cs | 29 --- test/api/dotnet/ClientTests.cs | 41 ---- test/extern/dotnet/ArraysTests.cs | 16 -- .../AWSEncryptionSDKTestVectors.csproj | 16 -- testVectors/README.md | 21 --- testVectors/TestVectors.cs | 175 ------------------ 20 files changed, 1025 deletions(-) delete mode 100644 nuget.config delete mode 100644 src/api/dotnet/CMMs.cs delete mode 100644 src/api/dotnet/Client.cs delete mode 100644 src/api/dotnet/DafnyFFI.cs delete mode 100644 src/api/dotnet/Keyrings.cs delete mode 100644 src/extern/dotnet/AESEncryption.cs delete mode 100644 src/extern/dotnet/Arrays-extern.cs delete mode 100644 src/extern/dotnet/HKDF-extern.cs delete mode 100644 src/extern/dotnet/KMS.cs delete mode 100644 src/extern/dotnet/RSAEncryption.cs delete mode 100644 src/extern/dotnet/Random.cs delete mode 100644 src/extern/dotnet/Signature.cs delete mode 100644 src/extern/dotnet/Time.cs delete mode 100644 src/extern/dotnet/UTF8.cs delete mode 100644 test/api/dotnet/ClientTests.cs delete mode 100644 test/extern/dotnet/ArraysTests.cs delete mode 100644 testVectors/AWSEncryptionSDKTestVectors.csproj delete mode 100644 testVectors/README.md delete mode 100644 testVectors/TestVectors.cs diff --git a/buildspec.yml b/buildspec.yml index fd3c73434..ce9cec9ce 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -28,7 +28,4 @@ phases: - export DAFNY_AWS_ESDK_TEST_VECTOR_MANIFEST_PATH="${PWD}/manifest.json" build: commands: - - dotnet build -t:VerifyDafny src - dotnet build -t:VerifyDafny test - - dotnet test - - dotnet test testVectors diff --git a/nuget.config b/nuget.config deleted file mode 100644 index ff01aaea3..000000000 --- a/nuget.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/api/dotnet/CMMs.cs b/src/api/dotnet/CMMs.cs deleted file mode 100644 index 6a9ab1d49..000000000 --- a/src/api/dotnet/CMMs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using CMMDefs; -using DefaultCMMDef; -using KeyringDefs; - -namespace AWSEncryptionSDK -{ - public class CMMs - { - public static CMM MakeDefaultCMM(Keyring keyring) - { - DefaultCMM result = new DefaultCMM(); - // TODO: The Dafny constructor shouldn't be directly callable from C# code! - // In particular, this isn't checking for a null keyring. - result.OfKeyring(keyring); - return result; - } - } -} diff --git a/src/api/dotnet/Client.cs b/src/api/dotnet/Client.cs deleted file mode 100644 index 3fa40b5ea..000000000 --- a/src/api/dotnet/Client.cs +++ /dev/null @@ -1,49 +0,0 @@ - -using System; -using System.IO; -using System.Collections.Generic; -using System.Linq; -using _System; -using CMMDefs; -using Dafny; -using byteseq = Dafny.Sequence; -using charseq = Dafny.Sequence; - -namespace AWSEncryptionSDK -{ - // TODO: What to name this? - public class Client { - - // TODO: Proper documentation - public static MemoryStream Encrypt(MemoryStream plaintext, CMM cmm, Dictionary encryptionContext) { - byteseq dafnyPlaintext = DafnyFFI.SequenceFromMemoryStream(plaintext); - Sequence> dafnyEncryptionContext = - ToDafnyEncryptionContext(encryptionContext); - - // TODO: This isn't checking for nulls or any of the requirements on the Dafny method. - // See https://github.com/dafny-lang/dafny/issues/461. - // TODO: Might need a lock here if ANYTHING in the Dafny runtime isn't threadsafe! - STL.Result result = ESDKClient.__default.Encrypt(dafnyPlaintext, cmm, dafnyEncryptionContext); - - return DafnyFFI.MemoryStreamFromSequence(DafnyFFI.ExtractResult(result)); - } - - // TODO: Proper documentation - public static MemoryStream Decrypt(MemoryStream cyphertext, CMM cmm) { - byteseq dafnyPlaintext = DafnyFFI.SequenceFromMemoryStream(cyphertext); - - // TODO: Might need a lock here if ANYTHING in the Dafny runtime isn't threadsafe! - STL.Result result = ESDKClient.__default.Decrypt(dafnyPlaintext, cmm); - - return DafnyFFI.MemoryStreamFromSequence(DafnyFFI.ExtractResult(result)); - } - - private static Sequence> - ToDafnyEncryptionContext(Dictionary encryptionContext) - { - IEnumerable> e = encryptionContext.Select(entry - => new Tuple2(DafnyFFI.DafnyUTF8BytesFromString(entry.Key), DafnyFFI.DafnyUTF8BytesFromString(entry.Value))); - return Sequence>.FromElements(e.ToArray()); - } - } -} diff --git a/src/api/dotnet/DafnyFFI.cs b/src/api/dotnet/DafnyFFI.cs deleted file mode 100644 index 3e4519520..000000000 --- a/src/api/dotnet/DafnyFFI.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.IO; -using System.Text; -using STL; - -using byteseq = Dafny.Sequence; -using charseq = Dafny.Sequence; - -// General-purpose utilities for invoking Dafny from C#, -// including converting between common Dafny and C# datatypes. -public class DafnyFFI { - - public static MemoryStream MemoryStreamFromSequence(byteseq seq) { - // TODO: Find a way to safely avoid copying - byte[] copy = new byte[seq.Elements.Length]; - Array.Copy(seq.Elements, 0, copy, 0, seq.Elements.Length); - return new MemoryStream(copy); - } - - public static byteseq SequenceFromMemoryStream(MemoryStream bytes) { - // TODO: Find a way to safely avoid copying - return byteseq.FromArray(bytes.ToArray()); - } - - public static string StringFromDafnyString(charseq dafnyString) { - // TODO: Find a way to safely avoid copying. - // The contents of a Dafny.Sequence should never change, but since a Dafny.ArraySequence - // currently allows direct access to its array we can't assume that's true. - return new string(dafnyString.Elements); - } - - public static charseq DafnyStringFromString(string s) { - // This is safe since string#ToCharArray() creates a fresh array - return charseq.FromArray(s.ToCharArray()); - } - - public static byteseq DafnyUTF8BytesFromString(string s) { - return byteseq.FromArray(Encoding.UTF8.GetBytes(s)); - } - - public static T ExtractResult(Result result) { - if (result is Result_Success s) { - return s.value; - } else if (result is Result_Failure f) { - // TODO-RS: Need to refine the wrapped value in a Failure so we - // can throw specific exception types. - throw new DafnyException(StringFromDafnyString(f.error)); - } else { - throw new ArgumentException(message: "Unrecognized STL.Result constructor"); - } - } - - public static Option NullableToOption(T t) - { - return t == null ? Option.create_None() : Option.create_Some(t); - } -} - -public class DafnyException : Exception { - public DafnyException(string message) : base(message) { - } -} diff --git a/src/api/dotnet/Keyrings.cs b/src/api/dotnet/Keyrings.cs deleted file mode 100644 index 717a4f5a1..000000000 --- a/src/api/dotnet/Keyrings.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using KeyringDefs; -using KMSKeyringDef; -using KMSUtils; -using STL; -using charseq = Dafny.Sequence; - -namespace AWSEncryptionSDK -{ - public class Keyrings - { - public static Keyring MakeKMSKeyring(ClientSupplier clientSupplier, - IEnumerable keyIDs, - string generator, - IEnumerable grantTokens) - { - KMSKeyring result = new KMSKeyring(); - // TODO: The Dafny constructor shouldn't be directly callable from C# code! - // This isn't checking for nulls or any other requirements. - result.__ctor( - clientSupplier, - Dafny.Sequence.FromElements(keyIDs.Select(DafnyFFI.DafnyStringFromString).ToArray()), - DafnyFFI.NullableToOption(generator != null ? DafnyFFI.DafnyStringFromString(generator) : null), - Dafny.Sequence.FromElements(grantTokens.Select(DafnyFFI.DafnyStringFromString).ToArray())); - return result; - } - } -} diff --git a/src/extern/dotnet/AESEncryption.cs b/src/extern/dotnet/AESEncryption.cs deleted file mode 100644 index e0544e526..000000000 --- a/src/extern/dotnet/AESEncryption.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Encodings; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Modes; -using Org.BouncyCastle.Crypto.Parameters; - -using byteseq = Dafny.Sequence; -using charseq = Dafny.Sequence; - - -namespace AESEncryption { - //TODO This code has yet to be reviewed. See issue #36 - public partial class AES_GCM { - - public static STL.Result AESEncrypt(EncryptionSuites.EncryptionSuite encAlg, - byteseq iv, - byteseq key, - byteseq msg, - byteseq aad) { - try { - var cipher = new GcmBlockCipher(new AesEngine()); - var param = new AeadParameters(new KeyParameter(key.Elements), (int)encAlg.tagLen * 8, iv.Elements, aad.Elements); - cipher.Init(true, param); - - byte[] c = new byte[cipher.GetOutputSize(msg.Elements.Length)]; - var len = cipher.ProcessBytes(msg.Elements, 0, msg.Elements.Length, c, 0); - cipher.DoFinal(c, len); //Append authentication tag to `c` - return new STL.Result_Success(__default.EncryptionOutputFromByteSeq(byteseq.FromArray(c), encAlg)); - } - catch { - return new STL.Result_Failure(charseq.FromArray("aes encrypt err".ToCharArray())); - } - } - - public static STL.Result AESDecrypt(EncryptionSuites.EncryptionSuite encAlg, byteseq key, byteseq cipherText, byteseq authTag, byteseq iv, byteseq aad) { - try { - var cipher = new GcmBlockCipher(new AesEngine()); - var param = new AeadParameters(new KeyParameter(key.Elements), encAlg.tagLen * 8, iv.Elements, aad.Elements); - cipher.Init(false, param); - var ctx = cipherText.Concat(authTag); - var pt = new byte[cipher.GetOutputSize(ctx.Elements.Length)]; - var len = cipher.ProcessBytes(ctx.Elements, 0, ctx.Elements.Length, pt, 0); - cipher.DoFinal(pt, len); //Check message authentication tag - return new STL.Result_Success(byteseq.FromArray(pt)); - } catch(InvalidCipherTextException macEx) { - return new STL.Result_Failure(charseq.FromArray(macEx.ToString().ToCharArray())); - } catch { - return new STL.Result_Failure(charseq.FromArray("aes decrypt err".ToCharArray())); - } - } - } -} diff --git a/src/extern/dotnet/Arrays-extern.cs b/src/extern/dotnet/Arrays-extern.cs deleted file mode 100644 index 460f31352..000000000 --- a/src/extern/dotnet/Arrays-extern.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Numerics; - -namespace Arrays { - - public partial class Array { - public static T[] copy(T[] source, BigInteger length) { - T[] dest = new T[(int)length]; - System.Array.Copy(source, dest, (int)length); - return dest; - } - public static void copyTo(T[] source, T[] dest, BigInteger offset) { - source.CopyTo(dest, (int)offset); - } - } -} diff --git a/src/extern/dotnet/HKDF-extern.cs b/src/extern/dotnet/HKDF-extern.cs deleted file mode 100644 index ef3c71917..000000000 --- a/src/extern/dotnet/HKDF-extern.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Numerics; - -namespace Utils { - - public static class Util { - public static int BigIntegerToInt(BigInteger x) { - try { - return (int) x; - } catch(OverflowException e) { - // TODO: error handling - Console.WriteLine(e.ToString()); - throw e; - } - } - } - - public class AlgorithmNotSupportedException : Exception - { - public AlgorithmNotSupportedException(string message) - : base(message) - { - } - } -} - - -namespace BouncyCastleCryptoMac { - - using Utils; - public partial class HMac { - - private Org.BouncyCastle.Crypto.Macs.HMac bcHMac; - - public HMac(Digests.HMAC_ALGORITHM algorithm) { - Org.BouncyCastle.Crypto.IDigest digest; - if(algorithm.is_HmacSHA256) { - digest = new Org.BouncyCastle.Crypto.Digests.Sha256Digest(); - bcHMac = new Org.BouncyCastle.Crypto.Macs.HMac(digest); - } else if(algorithm.is_HmacSHA384) { - digest = new Org.BouncyCastle.Crypto.Digests.Sha384Digest(); - bcHMac = new Org.BouncyCastle.Crypto.Macs.HMac(digest); - } else { - throw new AlgorithmNotSupportedException(algorithm.ToString() + " not supported."); - } - } - - public Dafny.Sequence getAlgorithmName() { - return Dafny.Sequence.FromString(bcHMac.AlgorithmName); - } - - public BigInteger getMacSize() { - return new BigInteger(bcHMac.GetMacSize()); - } - - public void init(CipherParameters ps) { - if(ps.is_KeyParameter) { - var bcKeyParameter = new Org.BouncyCastle.Crypto.Parameters.KeyParameter(ps.key); - bcHMac.Init(bcKeyParameter); - } - } - - public void reset() { - bcHMac.Reset(); - } - - public void updateSingle(byte input) { - bcHMac.Update(input); - } - - public void update(byte[] input , BigInteger inOff, BigInteger len) { - bcHMac.BlockUpdate(input, Util.BigIntegerToInt(inOff), Util.BigIntegerToInt(len)); - } - - public BigInteger doFinal(byte[] output, BigInteger outOff) { - return new BigInteger(bcHMac.DoFinal(output, Util.BigIntegerToInt(outOff))); - } - - public Org.BouncyCastle.Crypto.IDigest getUnderlyingDigest() { - return bcHMac.GetUnderlyingDigest(); - } - - } -} diff --git a/src/extern/dotnet/KMS.cs b/src/extern/dotnet/KMS.cs deleted file mode 100644 index 4c3cf8d18..000000000 --- a/src/extern/dotnet/KMS.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -using Amazon; - -using KMS = Amazon.KeyManagementService; -using DString = Dafny.Sequence; -using byteseq = Dafny.Sequence; -using EncryptionContext = Dafny.Sequence<_System.Tuple2,Dafny.Sequence>>; - -namespace KMSUtils { - public partial class __default { - //TODO: Issue #54 - public static Dictionary EncryptionContextToString(EncryptionContext encContext) { - UTF8Encoding utf8 = new UTF8Encoding(false, true); - Dictionary strDict = encContext.Elements.ToDictionary( - strKey => utf8.GetString(ConvertByteSeq(strKey._0)), - strElm => utf8.GetString(ConvertByteSeq(strElm._1)) - ); - return strDict; - } - - //TODO: Issue #54 - public static ResponseMetadata ConvertMetaData(Amazon.Runtime.ResponseMetadata rmd) { - Dafny.Map metadata = Dafny.Map - .FromCollection(rmd.Metadata.Select( - kvp => new Dafny.Pair((DString.FromString(kvp.Key.ToString())), (DString.FromString(kvp.Value.ToString()))) - ).ToList()); - DString requestID = DString.FromString(rmd.RequestId.ToString()); - return new ResponseMetadata(metadata, requestID); - } - public static byte[] ConvertByteSeq(byteseq bytes) { - return (byte[])bytes.Elements.Clone(); - } - } - public partial class DefaultClientSupplier : ClientSupplier { - public STL.Result GetClient(STL.Option region) { - if (region.is_Some) { - DString neverUsed = DString.FromString("".ToString()); - return new STL.Result_Success(new KMSClient(new KMS.AmazonKeyManagementServiceClient(RegionEndpoint.GetBySystemName(region.GetOrElse(neverUsed).ToString())))); - } else { - return new STL.Result_Failure(DString.FromString("Client Supplier does not have default region.".ToString())); - } - } - } - public partial class KMSClient { - - readonly private KMS.AmazonKeyManagementServiceClient client; - - public KMSClient(KMS.AmazonKeyManagementServiceClient client) { - this.client = client; - } - - public STL.Result GenerateDataKey(GenerateDataKeyRequest request) { - try { - KMS.Model.GenerateDataKeyRequest kmsRequest = new KMS.Model.GenerateDataKeyRequest() - { - EncryptionContext = __default.EncryptionContextToString(request.encryptionContext), - GrantTokens = request.grantTokens.Elements.Select(element => element.ToString()).ToList(), - KeyId = request.keyID.ToString(), - NumberOfBytes = request.numberOfBytes - }; - KMS.Model.GenerateDataKeyResponse response = this.client.GenerateDataKeyAsync(kmsRequest).Result; - return new STL.Result_Success(new GenerateDataKeyResponse( - byteseq.FromArray(response.CiphertextBlob.ToArray()), - response.ContentLength, - (int)response.HttpStatusCode, - DString.FromString(response.KeyId.ToString()), - byteseq.FromArray(response.Plaintext.ToArray()), - __default.ConvertMetaData(response.ResponseMetadata) - )); - } catch (Amazon.Runtime.AmazonServiceException amzEx) { - return new STL.Result_Failure(DString.FromString(amzEx.ToString())); - } catch (DecoderFallbackException decodeEx) { - return new STL.Result_Failure(DString.FromString(decodeEx.ToString())); - } - } - - public STL.Result Encrypt(EncryptRequest request) { - try { - KMS.Model.EncryptRequest kmsRequest = new KMS.Model.EncryptRequest() - { - EncryptionContext = __default.EncryptionContextToString(request.encryptionContext), - GrantTokens = request.grantTokens.Elements.Select(element => element.ToString()).ToList(), - KeyId = request.keyID.ToString(), - Plaintext = new MemoryStream(__default.ConvertByteSeq(request.plaintext)) - }; - KMS.Model.EncryptResponse response = this.client.EncryptAsync(kmsRequest).Result; - return new STL.Result_Success(new EncryptResponse( - byteseq.FromArray(response.CiphertextBlob.ToArray()), - response.ContentLength, - (int)response.HttpStatusCode, - DString.FromString(response.KeyId.ToString()), - __default.ConvertMetaData(response.ResponseMetadata) - )); - } catch (Amazon.Runtime.AmazonServiceException amzEx) { - return new STL.Result_Failure(DString.FromString(amzEx.ToString())); - } catch (DecoderFallbackException decodeEx) { - return new STL.Result_Failure(DString.FromString(decodeEx.ToString())); - } - } - - public STL.Result Decrypt(DecryptRequest request) { - try { - KMS.Model.DecryptRequest kmsRequest = new KMS.Model.DecryptRequest() - { - CiphertextBlob = new MemoryStream(__default.ConvertByteSeq(request.ciphertextBlob)), - EncryptionContext = __default.EncryptionContextToString(request.encryptionContext), - GrantTokens = request.grantTokens.Elements.Select(element => element.ToString()).ToList(), - }; - KMS.Model.DecryptResponse response = this.client.DecryptAsync(kmsRequest).Result; - return new STL.Result_Success(new DecryptResponse( - response.ContentLength, - (int)response.HttpStatusCode, - DString.FromString(response.KeyId.ToString()), - byteseq.FromArray(response.Plaintext.ToArray()), - __default.ConvertMetaData(response.ResponseMetadata) - )); - } catch (Amazon.Runtime.AmazonServiceException amzEx) { - return new STL.Result_Failure(DString.FromString(amzEx.ToString())); - } catch (DecoderFallbackException decodeEx) { - return new STL.Result_Failure(DString.FromString(decodeEx.ToString())); - } - } - } -} diff --git a/src/extern/dotnet/RSAEncryption.cs b/src/extern/dotnet/RSAEncryption.cs deleted file mode 100644 index 6301955c6..000000000 --- a/src/extern/dotnet/RSAEncryption.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Text; -using System.IO; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Encodings; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Security; - -using byteseq = Dafny.Sequence; -using charseq = Dafny.Sequence; - -namespace RSAEncryption { - - public class RSAUnsupportedPaddingSchemeException : Exception - { - public RSAUnsupportedPaddingSchemeException(string paddingScheme) - : base(String.Format("Invalid RSA Padding Scheme: {0}", paddingScheme)) - { - } - } - - public partial class RSA { - - // The following represent common, recommended RSA constants - const int RSA_PUBLIC_EXPONENT = (65537); - const int RSA_CERTAINTY = 256; - - // GetPemBytes represents a helper method that takes an AsymmetricCipherKeyPair and returns the corresponding - // private and public keys as UTF-8 byte arrays - private static void GetPemBytes(AsymmetricCipherKeyPair keyPair, out byte[] publicKeyBytes, out byte[] privateKeyBytes) { - using (var stringWriter = new StringWriter()) { - var pemWriter = new PemWriter(stringWriter); - pemWriter.WriteObject(keyPair.Public); - publicKeyBytes = Encoding.UTF8.GetBytes(stringWriter.ToString()); - } - - using (var stringWriter = new StringWriter()) { - var pemWriter = new PemWriter(stringWriter); - pemWriter.WriteObject(keyPair.Private); - privateKeyBytes = Encoding.UTF8.GetBytes(stringWriter.ToString()); - } - } - - // GetEngineForPadding represents a helper method that takes in an PaddingMode and returns a - // IAsymmetricBlockCipher for the RsaBlindedEngine that uses the appropriate digest or throws a - // RSAUnsupportedPaddingSchemeException if no valid padding exists - private static IAsymmetricBlockCipher GetEngineForPadding(PaddingMode padding) { - if (padding.is_PKCS1) { - return new Pkcs1Encoding(new RsaBlindedEngine()); - } else if (padding.is_OAEP__SHA1) { - return new OaepEncoding(new RsaBlindedEngine(), new Sha1Digest()); - } else if (padding.is_OAEP__SHA256) { - return new OaepEncoding(new RsaBlindedEngine(), new Sha256Digest()); - } else if (padding.is_OAEP__SHA384) { - return new OaepEncoding(new RsaBlindedEngine(), new Sha384Digest()); - } else if (padding.is_OAEP__SHA512) { - return new OaepEncoding(new RsaBlindedEngine(), new Sha512Digest()); - } else { - throw new RSAUnsupportedPaddingSchemeException(padding.ToString()); - } - } - - // GetPublicKeyFromByteSeq represents a helper method that takes in a byteseq representing a public - // key and returns the AsymmetricKeyParameter for that public key, encoded using UTF-8 - private static AsymmetricKeyParameter GetPublicKeyFromByteSeq(byteseq key) { - AsymmetricKeyParameter keyParam; - using (var stringReader = new StringReader(Encoding.UTF8.GetString(key.Elements))) { - return (AsymmetricKeyParameter) new PemReader(stringReader).ReadObject(); - } - } - - public static void GenerateKeyPairExtern(int strength, PaddingMode padding, out byteseq publicKey, out byteseq privateKey) { - 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(); - byte[] publicKeyBytes; - byte[] privateKeyBytes; - GetPemBytes(keygenPair, out publicKeyBytes, out privateKeyBytes); - publicKey = byteseq.FromArray(publicKeyBytes); - privateKey = byteseq.FromArray(privateKeyBytes); - } - - public static STL.Result EncryptExtern(PaddingMode padding, byteseq publicKey, byteseq plaintextMessage) { - try { - IAsymmetricBlockCipher engine = GetEngineForPadding(padding); - AsymmetricKeyParameter publicKeyParam = GetPublicKeyFromByteSeq(publicKey); - engine.Init(true, publicKeyParam); - return new STL.Result_Success(byteseq.FromArray( - engine.ProcessBlock(plaintextMessage.Elements, 0, plaintextMessage.Elements.Length))); - } - catch { - return new STL.Result_Failure(charseq.FromArray("rsa encrypt error".ToCharArray())); - } - } - - public static STL.Result DecryptExtern(PaddingMode padding, byteseq privateKey, byteseq cipherText) { - try { - IAsymmetricBlockCipher engine = GetEngineForPadding(padding); - AsymmetricCipherKeyPair keyPair; - using ( var stringReader = new StringReader(Encoding.UTF8.GetString(privateKey.Elements))) { - // This needs to be read as an AsymmetricCipherKeyPair and cannot be read directly as a - // AsymmetricKeyParameter like the public key can - keyPair = (AsymmetricCipherKeyPair) new PemReader(stringReader).ReadObject(); - } - engine.Init(false, keyPair.Private); - return new STL.Result_Success(byteseq.FromArray( - engine.ProcessBlock(cipherText.Elements, 0, cipherText.Elements.Length))); - } - catch { - return new STL.Result_Failure(charseq.FromArray("rsa decrypt error".ToCharArray())); - } - } - } -} diff --git a/src/extern/dotnet/Random.cs b/src/extern/dotnet/Random.cs deleted file mode 100644 index d7ec7ef95..000000000 --- a/src/extern/dotnet/Random.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Security.Cryptography; - -namespace Random { - public partial class __default { - public static Dafny.Sequence GenerateBytes(int i) { - RandomNumberGenerator rng = RandomNumberGenerator.Create(); - byte[] z = new byte[i]; - rng.GetBytes(z); - return Dafny.Sequence.FromArray(z); - } - } -} diff --git a/src/extern/dotnet/Signature.cs b/src/extern/dotnet/Signature.cs deleted file mode 100644 index ff8dc064e..000000000 --- a/src/extern/dotnet/Signature.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.EC; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Encodings; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Prng; -using Org.BouncyCastle.Crypto.Signers; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Math.EC; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Asn1; - -using byteseq = Dafny.Sequence; - -namespace Signature { - public partial class ECDSA { - public static STL.Option<_System.Tuple2> KeyGen(ECDSAParams x) { - try { - ECKeyPairGenerator g = new ECKeyPairGenerator(); - SecureRandom rng = new SecureRandom(); - X9ECParameters p; - if (x.is_ECDSA__P384) { - p = ECNamedCurveTable.GetByName("secp384r1"); - g.Init(new ECKeyGenerationParameters(new ECDomainParameters(p.Curve, p.G, p.N, p.H), rng)); - } else { // x is ECDSA__P256 - p = ECNamedCurveTable.GetByName("secp256r1"); - g.Init(new ECKeyGenerationParameters(new ECDomainParameters(p.Curve, p.G, p.N, p.H), rng)); - } - AsymmetricCipherKeyPair kp = g.GenerateKeyPair(); - ECPoint pt = ((ECPublicKeyParameters)kp.Public).Q; - byteseq vk = byteseq.FromArray(pt.GetEncoded()); - byteseq sk = byteseq.FromArray(((ECPrivateKeyParameters)kp.Private).D.ToByteArray()); - return new STL.Option_Some<_System.Tuple2>(new _System.Tuple2(vk, sk)); - } catch { - return new STL.Option_None<_System.Tuple2>(); - } - } - - public static bool Verify(ECDSAParams x, byteseq vk, byteseq digest, byteseq sig) { - try { - X9ECParameters p; - if (x.is_ECDSA__P384) { - p = ECNamedCurveTable.GetByName("secp384r1"); - } else { - p = ECNamedCurveTable.GetByName("secp256r1"); - } - ECDomainParameters dp = new ECDomainParameters(p.Curve, p.G, p.N, p.H); - ECPoint pt = p.Curve.DecodePoint(vk.Elements); - ECPublicKeyParameters vkp = new ECPublicKeyParameters(pt, dp); - ECDsaSigner sign = new ECDsaSigner(); - sign.Init(false, vkp); - BigInteger r, s; - DERDeserialize(sig.Elements, out r, out s); - return sign.VerifySignature(digest.Elements, r, s); - } catch { - return false; - } - } - - public static STL.Option Sign(ECDSAParams x, byteseq sk, byteseq digest) { - try { - X9ECParameters p; - if (x.is_ECDSA__P384) { - p = ECNamedCurveTable.GetByName("secp384r1"); - } else { - p = ECNamedCurveTable.GetByName("secp256r1"); - } - ECDomainParameters dp = new ECDomainParameters(p.Curve, p.G, p.N, p.H); - ECPrivateKeyParameters skp = new ECPrivateKeyParameters(new BigInteger(sk.Elements), dp); - ECDsaSigner sign = new ECDsaSigner(); - sign.Init(true, skp); - do { - BigInteger[] sig = sign.GenerateSignature(digest.Elements); - byte[] bytes = DERSerialize(sig[0], sig[1]); - if (bytes.Length != x.SignatureLength()) { - // Most of the time, a signature of the wrong length can be fixed - // by negating s in the signature relative to the group order. - bytes = DERSerialize(sig[0], p.N.Subtract(sig[1])); - } - if (bytes.Length == x.SignatureLength()) { - // This will meet the method postcondition, which says that a Some? return must - // contain a sequence of bytes whose length is x.SignatureLength(). - return new STL.Option_Some(byteseq.FromArray(bytes)); - } - // We only get here with low probability, so try again (forever, if we have really bad luck). - } while (true); - } catch { - return new STL.Option_None(); - } - } - - public static byteseq Digest(ECDSAParams x, byteseq msg) { - System.Security.Cryptography.HashAlgorithm alg; - if (x.is_ECDSA__P384) { - alg = System.Security.Cryptography.SHA384.Create(); - } else { - alg = System.Security.Cryptography.SHA256.Create(); - } - byte[] digest = alg.ComputeHash(msg.Elements); - return byteseq.FromArray(digest); - } - - private static byte[] DERSerialize(BigInteger r, BigInteger s) { - DerSequence derSeq = new DerSequence(new DerInteger(r), new DerInteger(s)); - return derSeq.GetEncoded(); - } - - private static void DERDeserialize(byte[] bytes, out BigInteger r, out BigInteger s) { - Asn1InputStream asn1 = new Asn1InputStream(bytes); - var seq = (Asn1Sequence)asn1.ReadObject(); - var dr = (DerInteger)seq[0]; - var ds = (DerInteger)seq[1]; - r = new BigInteger(dr.ToString()); - s = new BigInteger(ds.ToString()); - } - } -} diff --git a/src/extern/dotnet/Time.cs b/src/extern/dotnet/Time.cs deleted file mode 100644 index 10b9542e8..000000000 --- a/src/extern/dotnet/Time.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Diagnostics; -using System.Numerics; - -namespace Time { - //public partial class __default { - // public static BigInteger CurrentTimeMillis() { - // return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - // } - //} - - public class Timer { - private readonly Stopwatch stopwatch = new Stopwatch(); - - public Timer() { - stopwatch.Start(); - } - - public BigInteger ElapsedMilliseconds() { - return stopwatch.ElapsedMilliseconds; - } - } -} \ No newline at end of file diff --git a/src/extern/dotnet/UTF8.cs b/src/extern/dotnet/UTF8.cs deleted file mode 100644 index 0e2dcff8f..000000000 --- a/src/extern/dotnet/UTF8.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Text; - -using byteseq = Dafny.Sequence; -using charseq = Dafny.Sequence; - -namespace UTF8 { - public partial class __default { - public static STL.Result Encode(Dafny.Sequence str) { - UTF8Encoding utf8 = new UTF8Encoding(false, true); - try { - byte[] utf8Bytes = utf8.GetBytes(str.Elements); - return new STL.Result_Success(byteseq.FromArray(utf8Bytes)); - } catch(EncoderFallbackException e) { - return new STL.Result_Failure(charseq.FromArray("Input contains invalid Unicode characters".ToCharArray())); - } - } - - public static STL.Result> Decode(byteseq bytes) { - UTF8Encoding utf8 = new UTF8Encoding(false, true); - try { - string decoded = utf8.GetString(bytes.Elements); - return new STL.Result_Success>(charseq.FromArray(decoded.ToCharArray())); - } catch(DecoderFallbackException e) { - return new STL.Result_Failure>(charseq.FromArray("Input contains an invalid Unicode code point".ToCharArray())); - } - } - } -} diff --git a/test/api/dotnet/ClientTests.cs b/test/api/dotnet/ClientTests.cs deleted file mode 100644 index 8a2ead7bc..000000000 --- a/test/api/dotnet/ClientTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using KeyringDefs; -using KMSUtils; -using Xunit; - -using charseq = Dafny.Sequence; - - -namespace AWSEncryptionSDKTests -{ - public class ClientTests - { - [Fact] - public void RoundTripHappyPath() - { - String keyArn = DafnyFFI.StringFromDafnyString(TestUtils.__default.SHARED__TEST__KEY__ARN); - - ClientSupplier clientSupplier = new DefaultClientSupplier(); - - Keyring keyring = AWSEncryptionSDK.Keyrings.MakeKMSKeyring( - clientSupplier, Enumerable.Empty(), keyArn,Enumerable.Empty()); - - CMMDefs.CMM cmm = AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring); - - String plaintext = "Hello"; - MemoryStream plaintextStream = new MemoryStream(Encoding.UTF8.GetBytes(plaintext)); - - MemoryStream ciphertext = AWSEncryptionSDK.Client.Encrypt(plaintextStream, cmm, new Dictionary()); - - MemoryStream decodedStream = AWSEncryptionSDK.Client.Decrypt(ciphertext, cmm); - StreamReader reader = new StreamReader(decodedStream, Encoding.UTF8); - String decoded = reader.ReadToEnd(); - - Assert.Equal(plaintext, decoded); - } - } -} \ No newline at end of file diff --git a/test/extern/dotnet/ArraysTests.cs b/test/extern/dotnet/ArraysTests.cs deleted file mode 100644 index 683689c94..000000000 --- a/test/extern/dotnet/ArraysTests.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Numerics; -using Xunit; - -namespace EncryptionSDKTests -{ - public class ArraysTests - { - [Fact] - public void CopyShouldWork() - { - var myArray = new int[] {1, 2, 3, 4, 5}; - var copy = Arrays.Array.copy(myArray, new BigInteger(5)); - Assert.Equal(myArray, copy); - } - } -} \ No newline at end of file diff --git a/testVectors/AWSEncryptionSDKTestVectors.csproj b/testVectors/AWSEncryptionSDKTestVectors.csproj deleted file mode 100644 index d929871ac..000000000 --- a/testVectors/AWSEncryptionSDKTestVectors.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netcoreapp3.0 - false - false - - - - - - - - - - diff --git a/testVectors/README.md b/testVectors/README.md deleted file mode 100644 index 5e76874f8..000000000 --- a/testVectors/README.md +++ /dev/null @@ -1,21 +0,0 @@ -This directory holds dafny and C# code which decrypts the kms test vectors in https://github.com/awslabs/aws-encryption-sdk-test-vectors - -Download and unzip a set of vectors. - -Set the DAFNY_AWS_ESDK_TEST_VECTOR_MANIFEST_PATH environment variable as the absolute path of the manifest to use. - -``` -export DAFNY_AWS_ESDK_TEST_VECTOR_MANIFEST_PATH="" -``` - -To run the test vectors from this directory, run the following command: - -``` -dotnet test -``` - -To run the test vectors from the base directory, run the following command: - -``` -dotnet test testVectors -``` diff --git a/testVectors/TestVectors.cs b/testVectors/TestVectors.cs deleted file mode 100644 index 28f51f687..000000000 --- a/testVectors/TestVectors.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Linq; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using KeyringDefs; -using KMSUtils; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace TestVectorTests { - - public class TestVectorData : IEnumerable { - - public static Dictionary ParseKeys(string path) { - if (!File.Exists(path)) { - throw new ArgumentException($"Could not find keys file at path: {path}"); - } - string contents = System.IO.File.ReadAllText(path); - JObject keyManifest = JObject.Parse(contents); - JToken keys = keyManifest["keys"]; - if (keys == null) { - throw new ArgumentException($"Key file malformed: missing \"keys\" field"); - } - return keys.ToObject>(); - } - - public static Manifest ParseManifest(string path) { - if (!File.Exists(path)) { - throw new ArgumentException($"Could not find manifest file at path: {path}"); - } - string contents = System.IO.File.ReadAllText(path); - JObject manifest = JObject.Parse(contents); - - JToken tests = manifest["tests"]; - if (tests == null) { - throw new ArgumentException($"Manifest file malformed: missing \"tests\" field"); - } - - JToken keys = manifest["keys"]; - if (keys == null) { - throw new ArgumentException($"Manifest file malformed: missing \"keys\" field"); - } - - return new Manifest(tests.ToObject>(), keys.ToString()); - } - - public static string ManifestURIToPath(string uri, string manifestPath) { - // Assumes files referenced in manifests starts with 'file://' - if (!string.Equals(uri.Substring(0, 7), "file://")) { - throw new ArgumentException($"Malformed filepath in manifest (needs to start with 'file://'): {uri}"); - } - string parentDir = Directory.GetParent(manifestPath).ToString(); - - return Path.Combine(parentDir, uri.Substring(7)); - } - - public IEnumerator GetEnumerator() { - string manifestPath = Environment.GetEnvironmentVariable("DAFNY_AWS_ESDK_TEST_VECTOR_MANIFEST_PATH"); - if (manifestPath == null) { - throw new ArgumentException($"Environment Variable DAFNY_AWS_ESDK_TEST_VECTOR_MANIFEST_PATH must be set"); - } - - Manifest manifest = ParseManifest(manifestPath); - Dictionary vectorMap = manifest.vectorMap; - - string keysPath = ManifestURIToPath(manifest.keys, manifestPath); - Dictionary keyMap = ParseKeys(keysPath); - - foreach(var vectorEntry in vectorMap) { - string vectorID = vectorEntry.Key; - TestVector vector = vectorEntry.Value; - - string plaintextPath = ManifestURIToPath(vector.plaintext, manifestPath); - if (!File.Exists(plaintextPath)) { - throw new ArgumentException($"Could not find plaintext file at path: {plaintextPath}"); - } - byte[] plaintext = System.IO.File.ReadAllBytes(plaintextPath); - - string ciphertextPath = ManifestURIToPath(vector.ciphertext, manifestPath); - if (!File.Exists(plaintextPath)) { - throw new ArgumentException($"Could not find ciphertext file at path: {plaintextPath}"); - } - byte[] ciphertext = System.IO.File.ReadAllBytes(ManifestURIToPath(vector.ciphertext, manifestPath)); - - // TODO This is temporary logic to skip non-KMS test cases. Remove once testing all vectors. - bool isKMSTestVector = false; - foreach(var masterKey in vector.masterKeys) { - if (keyMap[masterKey.key].type == "aws-kms") { - isKMSTestVector = true; - break; - } - } - - if (!isKMSTestVector) { - continue; - } - - // Construct the KMS Keyring to test - // TODO This should be more generic. Master keys are set up with decryptable key at index 0 in current set of test vectors - MasterKey keyToTest = vector.masterKeys[0]; - ClientSupplier clientSupplier = new DefaultClientSupplier(); - Keyring keyring = AWSEncryptionSDK.Keyrings.MakeKMSKeyring( - clientSupplier, Enumerable.Empty(), keyMap[keyToTest.key].ID, Enumerable.Empty()); - CMMDefs.CMM cmm = AWSEncryptionSDK.CMMs.MakeDefaultCMM(keyring); - MemoryStream ciphertextStream = new MemoryStream(ciphertext); - - yield return new object[] { cmm, plaintext, ciphertextStream }; - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - // TODO Need to use some enums for various fields, possibly subtypes to represent RSA vs AES having different params? - public class Key { - public bool decrypt { get; set; } - public bool encrypt { get; set; } - public string type { get; set; } - [JsonProperty("key-id")] - public string ID { get; set; } - public string algorithm { get; set; } - public ushort bits { get; set; } - public string encoding { get; set; } - public string material { get; set; } - } - - // TODO Rename? Need to use some enums for various fields, possibly subtypes to represent RSA vs AES having different params? - public class MasterKey { - public string type { get; set; } - public string key { get; set; } - [JsonProperty("provider-id")] - public string providerID { get; set; } - [JsonProperty("encryption-algorithm")] - public string encryptionAlgorithm { get; set; } - [JsonProperty("padding-algorithm")] - public string paddingAlgorithm { get; set; } - [JsonProperty("padding-hash")] - public string paddingHash { get; set; } - } - - public class TestVector { - public string plaintext { get; set; } - public string ciphertext { get; set; } - [JsonProperty("master-keys")] - public IList masterKeys { get; set; } - } - - public class Manifest { - public Dictionary vectorMap { get; set; } - public string keys { get; set; } - - public Manifest(Dictionary vectorMap, string keys) { - this.vectorMap = vectorMap; - this.keys = keys; - } - } - - public class TestVectorDecryptTests { - [SkippableTheory] - [ClassData (typeof(TestVectorData))] - public void CanDecryptTestVector(CMMDefs.CMM cmm, byte[] expectedPlaintext, MemoryStream ciphertextStream) { - try { - MemoryStream decodedStream = AWSEncryptionSDK.Client.Decrypt(ciphertextStream, cmm); - byte[] result = decodedStream.ToArray(); - Assert.Equal(expectedPlaintext, result); - } catch (Exception e) when (string.Equals(e.Message, "Unframed Message Decryption Unimplemented")) { - // TODO While unframed message deserialization is unimplemented, test that the correct error is thrown - // TODO Ideally we would check against a specific, user-exposed Error class here - Skip.If(true); - } - } - } -} From f13832ba10b928f6443fef79cc4a872898b421a3 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sun, 24 May 2020 09:02:08 -0700 Subject: [PATCH 08/10] Add copyright notice (thanks, not-grep!) --- bench/SDK/Bench.dfy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bench/SDK/Bench.dfy b/bench/SDK/Bench.dfy index c40d20b53..e04bdb816 100644 --- a/bench/SDK/Bench.dfy +++ b/bench/SDK/Bench.dfy @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + include "../../src/SDK/Keyring/RawRSAKeyring.dfy" include "../../src/SDK/Materials.dfy" include "../../src/StandardLibrary/StandardLibrary.dfy" From 13a63218b5f4d12b5b463e5d95041d88aad0cebf Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Fri, 29 May 2020 09:55:29 -0700 Subject: [PATCH 09/10] Moving one more file to the Java repo --- .gitattributes | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 00a51aff5..000000000 --- a/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -# -# https://help.github.com/articles/dealing-with-line-endings/ -# -# These are explicitly windows files and should use crlf -*.bat text eol=crlf - From 45e9c807f98bf4ad831d4fc16edd2c7f2bced119 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 1 Jun 2020 10:48:16 -0700 Subject: [PATCH 10/10] Get Bench.dfy verifying, clean up style, use `expect` --- bench/SDK/Bench.dfy | 81 +++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/bench/SDK/Bench.dfy b/bench/SDK/Bench.dfy index e04bdb816..4cab232f4 100644 --- a/bench/SDK/Bench.dfy +++ b/bench/SDK/Bench.dfy @@ -12,6 +12,7 @@ include "../../src/SDK/MessageHeader.dfy" include "../../src/Crypto/RSAEncryption.dfy" include "../../src/Util/UTF8.dfy" include "../../src/StandardLibrary/Base64.dfy" +include "../../test/Util/TestUtils.dfy" module {:extern "Bench"} Bench { import opened StandardLibrary @@ -25,85 +26,65 @@ module {:extern "Bench"} Bench { import Msg = MessageHeader import UTF8 import Base64 + import TestUtils method Replicate(s : string, n : UInt.uint32) returns (ret : string) requires |s| * n as int < UInt.UINT32_LIMIT - ensures |ret| == |s| * n as int && forall i :: 0 <= i < |ret| ==> ret[i] == s[i % |s|] { - if n == 0 { - return ""; - } + ensures |ret| == |s| * n as int && forall i :: 0 <= i < |ret| ==> ret[i] == s[i % |s|] + { + if n == 0 { + return ""; + } - var l := |s| as UInt.uint32; - var a := new char[l * n]; - var i := 0 as UInt.uint32; + var l := |s| as UInt.uint32; + var a := new char[l * n]; + var i := 0 as UInt.uint32; - while i < l * n invariant 0 <= i <= l * n && forall j :: 0 <= j < i ==> a[j] == s[j % l] { - a[i] := s[i % l]; - i := i + 1; - } + while i < l * n invariant 0 <= i <= l * n && forall j :: 0 <= j < i ==> a[j] == s[j % l] { + a[i] := s[i % l]; + i := i + 1; + } - assert forall i :: 0 <= i < l * n ==> a[i] == s[i % l]; + assert forall i :: 0 <= i < l * n ==> a[i] == s[i % l]; - return a[..]; + return a[..]; } module {:extern "Time"} Time { - class {:extern "Timer"} Timer { - constructor {:extern} () { } + class {:extern "Timer"} Timer { + constructor {:extern} () { } method {:extern} ElapsedMilliseconds() returns (a : nat) - } + } } method Main() { - var namespace := UTF8.Encode("namespace"); - var name := UTF8.Encode("MyKeyring"); - if name.Failure? || namespace.Failure? { - print "Failure: hardcoded name/namespace cannot be utf8 encoded"; - return; - } + var namespace :- expect UTF8.Encode("namespace"); + var name :- expect UTF8.Encode("MyKeyring"); var ek, dk := RSAEncryption.GenerateKeyPair(2048, RSAEncryption.PKCS1); - var keyring := new RawRSAKeyringDef.RawRSAKeyring(namespace.value, name.value, RSAEncryption.PaddingMode.PKCS1, Some(ek), Some(dk)); + var keyring := new RawRSAKeyringDef.RawRSAKeyring(namespace, name, RSAEncryption.PaddingMode.PKCS1, Some(ek), Some(dk)); var cmm := new DefaultCMMDef.DefaultCMM.OfKeyring(keyring); var n := 1024 * 1024; var msg := Replicate("lorem ipsum dolor sit amet ", n); - var originalPlaintext := UTF8.Encode(msg).value; + var msgBytes :- expect UTF8.Encode(msg); + var originalPlaintext :- expect UTF8.Encode(msg); print "Plaintext size: ", |originalPlaintext|, "\n"; - var keyA, valA := UTF8.Encode("keyA").value, UTF8.Encode("valA").value; - var encryptionContext := [(keyA, valA)]; - assert Msg.ValidAAD(encryptionContext) by { - // To proving ValidAAD, we need to reveal the definition of ValidAAD: - reveal Msg.ValidAAD(); - // We also need to help the verifier with proving the AADLength is small: - calc { - Msg.AADLength(encryptionContext); - 2 + Msg.KVPairsLength(encryptionContext, 0, 1); - 2 + 2 + |keyA| + 2 + |valA|; - } - assert Msg.AADLength(encryptionContext) < UINT16_LIMIT; - } + var encryptionContext := TestUtils.SmallEncryptionContext(TestUtils.SmallEncryptionContextVariation.A); var timer := new Time.Timer(); - var e := Client.Encrypt(originalPlaintext, cmm, encryptionContext); - if e.Failure? { - print "Bad encryption :( ", e.error, "\n"; - return; - } + var encryptRequest := Client.EncryptRequest.WithCMM(msgBytes, cmm).SetEncryptionContext(encryptionContext); + var e :- expect Client.Encrypt(encryptRequest); - var d := Client.Decrypt(e.value, cmm); - if d.Failure? { - print "bad decryption: ", d.error, "\n"; - return; - } - var finalPlaintext := d.value; + var decryptRequest := Client.DecryptRequest.WithCMM(e, cmm); + var finalPlaintext :- expect Client.Decrypt(decryptRequest); var elapsed := timer.ElapsedMilliseconds(); - print elapsed, " ms\n"; - print "Match? ", (originalPlaintext == finalPlaintext), "\n"; + print elapsed, " ms\n"; + print "Match? ", (originalPlaintext == finalPlaintext), "\n"; } }