Commits

Anonymous committed 16df39c Draft

re-organised tests and switched from KeySpec to Algorithm

  • Participants
  • Parent commits 474423a

Comments (0)

Files changed (21)

 import java.security { SecureRandom }
 import java.lang { ByteArray }
 import javax.crypto.spec { IvParameterSpec }
-import toycrypto.keys { SharedKey }
+import toycrypto.spec { SharedKey }
 
 String transformation = "AES/OFB/NoPadding";
 Cipher cipher = fetchCipher(transformation);

File asn1/asn1.ceylon

+import ceylon.io.buffer { ByteBuffer, newByteBufferWithData }
+import ceylon.math.whole { Whole }
+
+import toycrypto.math { encodeWhole, decodeWhole }
+import toycrypto.spec { ByteSerialisable }
 import toycrypto.util { iterate, takeUntil, compareSequences }
-import ceylon.math.whole { Whole }
-import toycrypto.math { encodeWhole, decodeWhole }
-import ceylon.io.buffer { ByteBuffer, newByteBufferWithData }
 
 //
 // TOP-LEVEL TYPES
 
 shared interface ASN1Encodable
         of ASN1Decodable|ImplicitTag
-        satisfies ASN1Representable {
+        satisfies ASN1Representable & ByteSerialisable {
     shared formal Integer tag;
-    shared formal [Integer*] encodedBody;
+    shared formal [Integer*] body;
     
-    shared [Integer+] encoded {
-        value body = encodedBody;
-        return encodeSize(body.size).chain(body).sequence.withLeading(tag);
+    shared actual [Integer+] asBytes {
+        value body_ = body;
+        return encodeSize(body_.size).chain(body_).sequence.withLeading(tag);
     }
     
     shared actual ASN1Encodable asn1 => this;
 
     shared actual Integer tag =>
         contextTag.or(derTags.tagged).or(derTags.constructed);
-    shared actual Integer[] encodedBody => wrappedObject.encoded;
+
+    shared actual Integer[] body => wrappedObject.asBytes;
 }
 
 shared class ImplicitTag(contextTag, wrappedObject)
     shared ASN1Encodable wrappedObject;
     
     shared actual Integer tag => contextTag.or(derTags.tagged);
-    shared actual Integer[] encodedBody => wrappedObject.encodedBody;
+    shared actual Integer[] body => wrappedObject.body;
 }
 
 shared abstract class OpaqueImplicitTag(contextTag)
     shared formal ASN1BasicObject decodeAs(ASN1BasicObjectType type);
 }
 
-class OpaqueImplicitPrimitiveTag(Integer contextTag, encodedBody)
+class OpaqueImplicitPrimitiveTag(Integer contextTag, body)
         extends OpaqueImplicitTag(contextTag) {
-    shared actual [Integer*] encodedBody;
+    shared actual [Integer*] body;
     
     shared actual Boolean equals(Object that) {
         if (is OpaqueImplicitPrimitiveTag that) {
             return contextTag == that.contextTag &&
-                    encodedBody == that.encodedBody;
+                    body == that.body;
         } else {
             return false;
         }
     }
     
-    shared actual Integer hash => [contextTag.hash, encodedBody.hash].hash;
+    shared actual Integer hash => [contextTag.hash, body.hash].hash;
 
     shared actual ASN1BasicObject decodeAs(ASN1BasicObjectType type) =>
-        type.parse(type.tag, newByteBufferWithData(*encodedBody));
+        type.parse(type.tag, newByteBufferWithData(*body));
 }
 
 class OpaqueImplicitConstructedTag(Integer contextTag, sequence)
         extends OpaqueImplicitTag(contextTag) {
     [ASN1Encodable*] sequence;
     
-    shared actual [Integer*] encodedBody =>
-        join(*sequence.map((ASN1Encodable elem) => elem.encoded));
+    shared actual [Integer*] body =>
+        join(*sequence.map((ASN1Encodable elem) => elem.asBytes));
     
     shared actual Boolean equals(Object that) {
         if (is OpaqueImplicitConstructedTag that) {
             .reversed;
     }
 
-    shared actual [Integer+] encodedBody {
+    shared actual [Integer+] body {
         value first = sequence.first * 40;
         assert(exists second = sequence[1]);
         return join(*sequence[2...].map((Integer elem) => writeField(elem)))
         extends ASN1BasicObject(asn1SequenceType) {
     shared [ASN1Encodable*] sequence;
     
-    shared actual [Integer*] encodedBody =>
-        join(*sequence.map((ASN1Encodable elem) => elem.encoded));
-    
+    shared actual [Integer*] body =>
+        join(*sequence.map((ASN1Encodable elem) => elem.asBytes));
+
     shared actual String string => "ASN1Sequence(``", ".join(sequence.map((ASN1Encodable elem) => elem.string))``)";
     
     shared actual Boolean equals(Object that) {
         extends ASN1BasicObject(asn1SetType) {
     shared Set<ASN1Encodable> set;
     
-    shared actual [Integer*] encodedBody =>
-        join(*set.map((ASN1Encodable elem) => elem.encoded)
-                 .sort(compareSequences<Integer>));
+    shared actual [Integer*] body =>
+        join(*set.map((ASN1Encodable elem) => elem.asBytes)
+            .sort(compareSequences<Integer>));
     
     shared actual String string => "ASN1Set(``", ".join(set.map((ASN1Encodable elem) => elem.string))``)";
     
         extends ASN1BasicObject(asn1IntegerType) {
     shared Whole whole;
     
-    shared actual [Integer+] encodedBody => encodeWhole(whole);
+    shared actual [Integer+] body => encodeWhole(whole);
     
     shared actual String string => "ASN1Integer(``whole``)";
     
 shared class OctetString(bytes)
         extends ASN1BasicObject(octetStringType) {
     shared [Integer*] bytes;
-    
-    shared actual [Integer*] encodedBody = bytes;
-    
-    shared actual String string => "OctetString(``bytes``)";
-    
+    shared actual [Integer*] body = bytes;
+    shared actual String string => "OctetString(``bytes``)";   
     shared actual Boolean equals(Object that) {
         if (is OctetString that) {
             return bytes == that.bytes;
     shared Integer padBits;
     assert(padBits < 8);
     
-    shared actual [Integer+] encodedBody => bytes.withLeading(padBits);
+    shared actual [Integer+] body => bytes.withLeading(padBits);
     
-    shared actual String string => "BitString(``bytes``)";
-    
+    shared actual String string => "BitString(``bytes``)";   
     shared actual Boolean equals(Object that) {
         if (is BitString that) {
             return bytes == that.bytes;

File asn1/cms.ceylon

 import ceylon.math.whole { wholeNumber }
 
-abstract class ContentType([Integer+] identifier)
+shared abstract class ContentType([Integer+] identifier)
     of dataContentType|signedDataContentType|envelopedDataContentType
     extends ObjectIdentifier(identifier) {}
 
 //          us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 }
 object envelopedDataContentType extends ContentType(pkcs7.sequence.withTrailing(3)) {}
 
-abstract class ContentInfo(contentType)
+shared abstract class ContentInfo(contentType)
         of ArbitraryData|SignedData|EnvelopedData
         satisfies ASN1Representable {
     shared ContentType contentType;
     
     shared actual ASN1Sequence asn1 => ASN1Sequence([
         contentInfo.contentType,
-        ExplicitTag(0, OctetString(contentInfo.content.asn1.encoded))
+        ExplicitTag(0, OctetString(contentInfo.content.asn1.asBytes))
     ]);
 }
 
-class ArbitraryData(content)
+shared class ArbitraryData(content)
         extends ContentInfo(dataContentType) {
     shared actual ASN1Representable content;
 }
 }
 
 ContentInfo createContentInfo(ObjectIdentifier identifier, ASN1Representable content) {
+    assert(is ASN1Decodable content);
     switch (checkContentType(identifier))
     case (dataContentType) {
         return ArbitraryData(content);
     }
 }
 
-ContentInfo asn1ContentInfo(ASN1Sequence asn1) {
+shared ContentInfo asn1ContentInfo(ASN1Sequence asn1) {
     value seq = asn1.sequence;
     assert(seq.size == 2);
     assert(is ObjectIdentifier identifier = seq.first);

File asn1/run.ceylon

-[Integer+] wrap(String message) {
-    value data = OctetString(message.characters.collect((Character elem) => elem.integer));
-    return ArbitraryData(data).asn1.encoded;
-}
-
-String unwrap([Integer+] bytes) {
-    value container = asn1Decodable(bytes); 
-    assert(is ASN1Sequence container);
-    assert(is ArbitraryData contentInfo = asn1ContentInfo(container));
-    assert(is OctetString codepoints = contentInfo.content);
-    return string(codepoints.bytes.collect((Integer element) => element.character));
-}
-
-doc "Run the module `toycrypto`."
-void run() {
-    value message = "Hello world!";
-    value container = wrap(message);
-    assert(message == unwrap(container));
-}
 import toycrypto.primitives { randomBytes, newKeysFromSharedSecret, randomKeyParts, ECDHKeyParts, combineToSharedSecret }
 import toycrypto.math { Point, Domain }
-import toycrypto.keys { KeySpec, SharedKey }
+import toycrypto.spec { SharedKey, aes256, hmacSha1, ByteSerialisable }
 import toycrypto.asn1 { ECPublicKeyInfo, asn1ECPublicKeyInfo, ASN1Sequence, asn1Decodable, OctetString, ASN1Representable }
 import toycrypto.util { compareSequences }
 
 
 shared abstract class EphemeralKey([Integer+] sharedSecret, [Integer+] sharedNonce) {
     value keys = newKeysFromSharedSecret {
-        keySpecs = [KeySpec(256 / 8, "AES"), KeySpec(256 / 8, "HmacSHA1")];
+        algorithms = [aes256, hmacSha1];
         sharedSecret = sharedSecret;
         info = "EphemeralKey";
         sharedNonce = sharedNonce;
     shared actual Integer hash => [encryptionKey.hash, hmacKey.hash].hash;
 }
 
-shared abstract class EphemeralSharedPart() satisfies ASN1Representable {
+shared abstract class EphemeralSharedPart()
+        satisfies ASN1Representable & ByteSerialisable {
     shared formal Domain domain;
     shared formal Point q;
     shared formal [Integer+] nonce;
         ECPublicKeyInfo(domain, q).asn1
     ]);
     
-    shared [Integer+] encoded => asn1.encoded;
+    shared actual [Integer+] asBytes => asn1.asBytes;
     
     shared actual Boolean equals(Object that) {
         if (is EphemeralSharedPart that) {

File keys/interfaces.ceylon

-shared interface Encodable {
-    shared formal [Integer+] encoded;
-}
-
-shared abstract class KeyPrivacy(String name) of me|everyone|recipients {
-    shared actual String string => name;
-}
-
-shared object me extends KeyPrivacy("me") {}
-shared object everyone extends KeyPrivacy("everyone") {}
-shared object recipients extends KeyPrivacy("recipients") {}
-
-shared interface Key satisfies Encodable {
-    shared formal KeySpec keySpec;
-    shared formal KeyPrivacy privacy;
-}

File keys/keys.ceylon

-import javax.crypto { JavaSecretKey = SecretKey }
-import javax.crypto.spec { SecretKeySpec }
-import java.lang { arrays }
-
-shared class KeySpec(byteLength, algorithm) {
-    assert(byteLength > 0);
-    shared Integer byteLength;
-    shared String algorithm;
-}
-
-// TODO: move to module top-level
-shared class SharedKey(encoded, String algorithm)
-        satisfies Key {
-    shared JavaSecretKey javaKey = SecretKeySpec(arrays.toByteArray(encoded), algorithm);
-    
-    shared actual KeySpec keySpec => KeySpec(encoded.size, algorithm);
-    
-    shared actual [Integer+] encoded;
-    
-    shared actual KeyPrivacy privacy = recipients;
-    
-    shared actual Boolean equals(Object that) {
-        if (is SharedKey that) {
-            return javaKey == that.javaKey;
-        } else {
-            return false;
-        }
-    }
-    
-    shared actual Integer hash => [javaKey.hash].hash;
-}

File keys/package.ceylon

-shared package toycrypto.keys;

File primitives/ecdh.ceylon

 import toycrypto.util { decodeLowerHex }
 
 // from org.bouncycastle.asn1.x9.X962NamedCurves
-Domain prime192v1() {
+shared Domain prime192v1() {
     assert(exists p = parseWhole("6277101735386680763835789423207666416083908700390324961279"));
     // fffffffffffffffffffffffffffffffefffffffffffffffc
     assert(exists a = parseWhole("6277101735386680763835789423207666416083908700390324961276"));

File primitives/keys.ceylon

 import org.bouncycastle.crypto.digests { WhirlpoolDigest }
 import org.bouncycastle.crypto.params { HKDFParameters }
 import ceylon.io.buffer { newByteBuffer }
-import toycrypto.keys { SharedKey, KeySpec }
+import toycrypto.spec { SharedKey, FixedKeySizeAlgorithm }
 import toycrypto.util { codepointsOf }
 
 // Create keys from the shared secret as in RFC 5996
 // (https://tools.ietf.org/html/rfc5996#section-2.13)
-shared [SharedKey+] newKeysFromSharedSecret([KeySpec+] keySpecs, [Integer+] sharedSecret, String info, [Integer+] sharedNonce = nothing) {
-    value concatenatedKeys = newByteBuffer(keySpecs.fold(0, (Integer partial, KeySpec elem) => partial + elem.byteLength));
+shared [SharedKey+] newKeysFromSharedSecret(
+        [FixedKeySizeAlgorithm+] algorithms,
+        [Integer+] sharedSecret,
+        String info,
+        [Integer+] sharedNonce = nothing) {
+    value concatenatedKeys = newByteBuffer(sum(algorithms.collect((FixedKeySizeAlgorithm elem) => elem.keyByteSize)));
     value hkdf = HKDFBytesGenerator(WhirlpoolDigest());
     hkdf.init(HKDFParameters(arrays.toByteArray(sharedSecret),
         !sharedNonce.empty then arrays.toByteArray(sharedNonce) else null,
         arrays.toByteArray(codepointsOf(info))));
     hkdf.generateBytes(arrays.asByteArray(concatenatedKeys.bytes()), 0, concatenatedKeys.size);
     concatenatedKeys.flip();
-    return keySpecs.collect((KeySpec spec) {
-        assert(nonempty keyBytes = concatenatedKeys.taking(spec.byteLength).sequence);
-        return SharedKey(keyBytes, spec.algorithm);
+    return algorithms.collect((FixedKeySizeAlgorithm algorithm) {
+        assert(nonempty keyBytes = concatenatedKeys.taking(algorithm.keyByteSize).sequence);
+        return SharedKey(keyBytes, algorithm);
     });
 }

File spec/algorithms.ceylon

+shared interface NamedAlgorithm {
+    shared formal String name;
+}
+
+shared interface FixedKeySizeAlgorithm
+        satisfies NamedAlgorithm {
+    shared formal Integer keyByteSize;
+}
+
+shared interface SymmetricEncryptionAlgorithm
+        of AES
+        satisfies NamedAlgorithm {}
+
+shared interface AES
+        of aes256
+        satisfies SymmetricEncryptionAlgorithm & FixedKeySizeAlgorithm {}
+
+shared object aes256
+        satisfies AES {
+    shared actual String name = "AES";
+    shared actual Integer keyByteSize = 256 / 8;
+}
+
+shared interface HmacAlgorithm
+        satisfies NamedAlgorithm {}
+
+shared object hmacSha1
+        satisfies HmacAlgorithm & FixedKeySizeAlgorithm {
+    shared actual String name = "HmacSHA1";
+    shared actual Integer keyByteSize = 256 / 8; // TODO: check
+}

File spec/interfaces.ceylon

+shared interface ByteSerialisable {
+    shared formal [Integer+] asBytes;
+}

File spec/keys.ceylon

+import javax.crypto { JavaSecretKey = SecretKey }
+import javax.crypto.spec { SecretKeySpec }
+import java.lang { arrays }
+
+shared abstract class KeyPrivacy(String name)
+        of me|everyone|recipients {
+    shared actual String string => name;
+}
+
+shared object me extends KeyPrivacy("me") {}
+shared object everyone extends KeyPrivacy("everyone") {}
+shared object recipients extends KeyPrivacy("recipients") {}
+
+shared interface Key satisfies ByteSerialisable {
+    shared formal NamedAlgorithm algorithm;
+    shared formal KeyPrivacy privacy;
+}
+
+shared class SharedKey([Integer+] bytes, algorithm)
+        satisfies Key {
+    shared JavaSecretKey javaKey = SecretKeySpec(arrays.toByteArray(bytes), algorithm.name);
+    
+    shared actual FixedKeySizeAlgorithm algorithm;
+    
+    shared actual [Integer+] asBytes = bytes;
+    
+    shared actual KeyPrivacy privacy = recipients;
+    
+    shared actual Boolean equals(Object that) {
+        if (is SharedKey that) {
+            return javaKey == that.javaKey;
+        } else {
+            return false;
+        }
+    }
+    
+    shared actual Integer hash => [javaKey.hash].hash;
+}

File spec/package.ceylon

+shared package toycrypto.spec;

File test/asn1.ceylon

+import ceylon.math.whole { wholeNumber }
+import java.lang { arrays }
+
+import java.security { KeyFactory { fetchKeyFactory = getInstance } }
+import java.security.spec { X509EncodedKeySpec }
+
+import org.bouncycastle.asn1 { JavaASN1Seq = ASN1Sequence { javaASN1Sequence = getInstance }, JavaASN1Primitive = ASN1Primitive { parsePrimitive = fromByteArray }, ASN1ObjectIdentifier, JavaASN1Integer = ASN1Integer }
+import org.bouncycastle.asn1.x509 { JavaSubjectPublicKeyInfo = SubjectPublicKeyInfo { toSubjectPublicKeyInfo = getInstance } }
+import org.bouncycastle.asn1.x9 { X9ObjectIdentifiers { id_ecPublicKey } }
+
+import toycrypto.asn1 { ASN1Representable, asn1Decodable, ASN1Integer, x9Identifiers, X9FieldId, X9Curve, X9ECPoint, X9ECParameters, AlgorithmIdentifier, encodePoint, BitString, ASN1Sequence, SubjectPublicKeyInfo, ECPublicKeyInfo }
+import toycrypto.math { Domain }
+import toycrypto.primitives { prime192v1 }
+import toycrypto.util { takeUntil, iterate }
+
+Domain domain = prime192v1();
+
+doc "Run the tests for the package `toycrypto.asn1`."
+void asn1() {
+    writeField();
+    deencodes();
+    decodeJavaSharedPart();
+}
+
+void writeField() {
+    // from toycrypto.asn1.ObjectIdentifier
+    [Integer+] writeField(Integer field) {
+        return takeUntil(
+                iterate(field, (Integer i) => i.rightLogicalShift(7)),
+                (Integer i) => i == 0)
+            .collect((Integer elem) => elem.and(#7f).or(#80))
+            .withLeading(field.and(#7f))
+            .reversed;
+    }
+    
+    assert(writeField(0) == [0]);
+    assert(writeField(1) == [1]);
+    assert(writeField(255) == [129, 127]);
+}
+
+void deencodes() {
+    JavaASN1Primitive deencode(ASN1Representable encodable) {
+        value asn1 = encodable.asn1;
+        value encoded = asn1.asBytes;
+        value aSN1Object = asn1Decodable(encoded);
+        assert(aSN1Object == asn1);
+        return parsePrimitive(arrays.toByteArray(encoded));
+    }
+    
+    value int = ASN1Integer(wholeNumber(512));
+    value decInt = deencode(int);
+    assert(is JavaASN1Integer decInt);
+    //print(decInt);
+    
+    value identifier = x9Identifiers.ecPublicKey;
+    value decIdentifier = deencode(identifier);
+    assert(is ASN1ObjectIdentifier decIdentifier);
+    assert(decIdentifier.string == identifier.string);
+    //print(decIdentifier);
+    
+    value fieldId = X9FieldId(domain.curve.primeP);
+    value decFieldId = deencode(fieldId);
+    value decSeqFieldId = javaASN1Sequence(decFieldId);
+    //print(decSeqFieldId);
+    
+    value curve = X9Curve(domain.curve);
+    value decCurve = deencode(curve);
+    value decSeqCurve = javaASN1Sequence(decCurve);
+    //print(decSeqCurve);
+    
+    value ecPoint = X9ECPoint(domain.basepointG);
+    value decEcPoint = deencode(ecPoint);
+    //print(decEcPoint);
+    
+    value params = X9ECParameters(domain);
+    value decParams = deencode(params);
+    value decSeqParams = javaASN1Sequence(decParams);
+    //print(decSeqParams);
+    
+    value algorithmIdentifier = AlgorithmIdentifier(identifier, params);
+    value decAlgorithmIdentifier = deencode(algorithmIdentifier);
+    value decSeqAlgorithmIdentifier = javaASN1Sequence(decAlgorithmIdentifier);
+    //print(decSeqAlgorithmIdentifier);
+    
+    value bitString = BitString([0]);
+    value testSeq = ASN1Sequence([bitString, identifier]);
+    //print("Size[1]: " + Size(1).encoded.string);
+    //print("BitString: " + bitString.encoded.string);
+    //print("Identifier: " + identifier.encoded.string);
+    //print("TestSeq: " + testSeq.asn1.encoded.string);
+    value decTestSeq = deencode(testSeq);
+    value decSeqTestSeq = javaASN1Sequence(decTestSeq);
+    //print(decSeqTestSeq);
+    
+    value publicKey = SubjectPublicKeyInfo(algorithmIdentifier, encodePoint(domain.basepointG));
+    value decPublicKey = deencode(publicKey);
+    value decSeqPublicKey = javaASN1Sequence(decPublicKey);
+    //print(decSeqPublicKey);
+}
+
+void decodeJavaSharedPart() {
+    value keySeq = ECPublicKeyInfo(domain, domain.basepointG).asn1;
+    value encodedKey = arrays.toByteArray(keySeq.asBytes);
+    
+    value subPubKeyInfo = toSubjectPublicKeyInfo(encodedKey);
+    assert(subPubKeyInfo.algorithm.algorithm == id_ecPublicKey);
+    assert(subPubKeyInfo.publicKeyData.bytes.array.sequence == encodePoint(domain.basepointG));
+    
+    value encodedKeySpec = X509EncodedKeySpec(encodedKey);
+    // Test actual validity
+    fetchKeyFactory("ECDH").generatePublic(encodedKeySpec);
+}

File test/cms.ceylon

+import toycrypto.asn1 { OctetString, ArbitraryData, asn1Decodable, ASN1Sequence, asn1ContentInfo }
+
+[Integer+] wrap(String message) {
+    value data = OctetString(message.characters.collect((Character elem) => elem.integer));
+    return ArbitraryData(data).asn1.asBytes;
+}
+
+String unwrap([Integer+] bytes) {
+    value container = asn1Decodable(bytes); 
+    assert(is ASN1Sequence container);
+    assert(is ArbitraryData contentInfo = asn1ContentInfo(container));
+    assert(is OctetString codepoints = contentInfo.content);
+    return string(codepoints.bytes.collect((Integer element) => element.character));
+}
+
+doc "Run the CMS-related tests for the package `toycrypto.asn1`."
+void cms() {
+    value message = "Hello world!";
+    value container = wrap(message);
+    assert(message == unwrap(container));
+}

File test/math.ceylon

+import ceylon.math.whole { wholeNumber }
+
+import java.math { BigInteger { bigInteger = valueOf } }
+
+import org.bouncycastle.math.ec { ECFieldElement { Fp } }
+
+import toycrypto.math { sqrtModuloPrime, two, encodeWhole, decodeWhole }
+
+doc "Run the tests for the package `toycrypto.math`."
+void math() {
+    whole();
+    sqrt();
+}
+
+void whole() {
+    value w = wholeNumber(512);
+    value bytes = encodeWhole(w);
+    assert(decodeWhole(bytes) == w);
+}
+
+void sqrt() {
+    value biTwo = bigInteger(2);
+    
+    void test(Integer x, Integer modulus) {
+        value biModulus = bigInteger(modulus);
+        value biX = bigInteger(x);
+        value fe = Fp(biModulus, biX);
+        assert(fe.sqrt().toBigInteger().modPow(biTwo, biModulus) == biX);
+        
+        value wX = wholeNumber(x);
+        value wModulus = wholeNumber(modulus);
+        assert(sqrtModuloPrime(wX, wModulus).powerRemainder(two, wModulus) == wX);
+    }
+    
+    test(4, 7);
+    test(4, 13);
+}

File test/primitives.ceylon

+import toycrypto.primitives { randomKeyParts }
+
+doc "Run the tests for the package `toycrypto.primitives`."
+void primitives() {
+    randomKeyParts();
+}

File test/root.ceylon

+import java.lang { arrays, ByteArray }
+
+import toycrypto { CipherText, IncompleteEphemeralKey, encrypt, decrypt, EphemeralKey, EphemeralSharedPart, decodeSharedPart }
+import toycrypto.spec { SharedKey }
+import toycrypto.util { codepointsOf, asCodepoints }
+
+doc "Run the tests for the package `toycrypto`."
+void root() {
+    value key = ecdh().encryptionKey;
+    encodeSharedPart();
+    aes(key);
+}
+
+EphemeralKey ecdh() {
+    value alice = IncompleteEphemeralKey();
+    value bob = IncompleteEphemeralKey();
+    
+    EphemeralKey aliceSecret = alice.complete { remoteSharedPart = bob.sharedPart; };
+    EphemeralKey bobSecret = bob.complete { remoteSharedPart = alice.sharedPart; };
+    assert(aliceSecret == bobSecret);
+    
+    return aliceSecret;
+}
+
+void encodeSharedPart() {
+    EphemeralSharedPart sharedPart = IncompleteEphemeralKey().sharedPart;
+    
+    value encodedSharedPart = sharedPart.asBytes;
+    EphemeralSharedPart decodedPart = decodeSharedPart(encodedSharedPart);
+    assert(decodedPart == sharedPart);
+}
+
+void aes(SharedKey key) {
+    String message = "Hello world!";
+    CipherText enc = encrypt(key, arrays.toByteArray(codepointsOf(message)));
+    
+    ByteArray dec = decrypt(key, enc);
+    String fin = string(asCodepoints(dec.array));
+    assert(fin == message);
+    print(fin);
+}

File test/run.ceylon

-import ceylon.math.whole { wholeNumber }
+import java.security { Security { addSecurityProvider = addProvider } }
 
-import java.lang { arrays, ByteArray }
-import java.math { BigInteger { bigInteger = valueOf } }
-import java.security { Security { addSecurityProvider = addProvider }, KeyFactory { fetchKeyFactory = getInstance } }
-import java.security.spec { X509EncodedKeySpec }
-
-import org.bouncycastle.asn1 { JavaASN1Seq = ASN1Sequence { toASN1Sequence = getInstance }, ASN1Primitive { parsePrimitive = fromByteArray }, ASN1ObjectIdentifier, JavaASN1Integer = ASN1Integer }
-import org.bouncycastle.asn1.x509 { JavaSubjectPublicKeyInfo = SubjectPublicKeyInfo { toSubjectPublicKeyInfo = getInstance } }
-import org.bouncycastle.asn1.x9 { X9ObjectIdentifiers { id_ecPublicKey } }
 import org.bouncycastle.jce.provider { BouncyCastleProvider }
-import org.bouncycastle.math.ec { ECFieldElement { Fp } }
-
-import toycrypto { CipherText, IncompleteEphemeralKey, encrypt, decrypt, EphemeralKey, EphemeralSharedPart, decodeSharedPart }
-import toycrypto.asn1 { X9ECParameters, ASN1Representable, x9Identifiers, AlgorithmIdentifier, BitString, encodePoint, SubjectPublicKeyInfo, ASN1Sequence, asn1Decodable, ASN1Integer, X9FieldId, X9Curve, X9ECPoint }
-import toycrypto.keys { SharedKey }
-import toycrypto.math { sqrtModuloPrime, two, encodeWhole, decodeWhole }
-import toycrypto.primitives { randomKeyParts }
-import toycrypto.util { codepointsOf, asCodepoints, forever, takeUntil, iterate }
 
 doc "Run the tests for the module `toycrypto`."
 void run() {
     addSecurityProvider(BouncyCastleProvider());
-    whole();
-    sqrt();
-    keyParts();
-    value key = ecdh().encryptionKey;
-    writeField();
-    encodeSharedPart();
-    aes(key);
-}
-
-void whole() {
-    value w = wholeNumber(512);
-    value bytes = encodeWhole(w);
-    assert(decodeWhole(bytes) == w);
-}
-
-void sqrt() {
-    value biTwo = bigInteger(2);
     
-    void test(Integer x, Integer modulus) {
-        value biModulus = bigInteger(modulus);
-        value biX = bigInteger(x);
-        value fe = Fp(biModulus, biX);
-        assert(fe.sqrt().toBigInteger().modPow(biTwo, biModulus) == biX);
-        
-        value wX = wholeNumber(x);
-        value wModulus = wholeNumber(modulus);
-        assert(sqrtModuloPrime(wX, wModulus).powerRemainder(two, wModulus) == wX);
-    }
-    
-    test(4, 7);
-    test(4, 13);
-}
-
-void keyParts() {
-    forever(() => 1).find((Integer elem) => true);
-    randomKeyParts();
-}
-
-EphemeralKey ecdh() {
-    value alice = IncompleteEphemeralKey();
-    value bob = IncompleteEphemeralKey();
-    
-    EphemeralKey aliceSecret = alice.complete { remoteSharedPart = bob.sharedPart; };
-    EphemeralKey bobSecret = bob.complete { remoteSharedPart = alice.sharedPart; };
-    assert(aliceSecret == bobSecret);
-    
-    return aliceSecret;
-}
-
-void writeField() {
-    // from toycrypto.asn1.ObjectIdentifier
-    [Integer+] writeField(Integer field) {
-        return takeUntil(
-                iterate(field, (Integer i) => i.rightLogicalShift(7)),
-                (Integer i) => i == 0)
-            .collect((Integer elem) => elem.and(#7f).or(#80))
-            .withLeading(field.and(#7f))
-            .reversed;
-    }
-    
-    assert(writeField(0) == [0]);
-    assert(writeField(1) == [1]);
-    assert(writeField(255) == [129, 127]);
-}
-
-void deencodeParts(EphemeralSharedPart sharedPart) {
-    ASN1Primitive deencode(ASN1Representable encodable) {
-        value asn1 = encodable.asn1;
-        value encoded = asn1.encoded;
-        value aSN1Object = asn1Decodable(encoded);
-        assert(aSN1Object == asn1);
-        return parsePrimitive(arrays.toByteArray(encoded));
-    }
-    
-    value int = ASN1Integer(wholeNumber(512));
-    value decInt = deencode(int);
-    assert(is JavaASN1Integer decInt);
-    //print(decInt);
-    
-    value identifier = x9Identifiers.ecPublicKey;
-    value decIdentifier = deencode(identifier);
-    assert(is ASN1ObjectIdentifier decIdentifier);
-    assert(decIdentifier.string == identifier.string);
-    //print(decIdentifier);
-    
-    value fieldId = X9FieldId(sharedPart.domain.curve.primeP);
-    value decFieldId = deencode(fieldId);
-    value decSeqFieldId = toASN1Sequence(decFieldId);
-    //print(decSeqFieldId);
-    
-    value curve = X9Curve(sharedPart.domain.curve);
-    value decCurve = deencode(curve);
-    value decSeqCurve = toASN1Sequence(decCurve);
-    //print(decSeqCurve);
-    
-    value ecPoint = X9ECPoint(sharedPart.domain.basepointG);
-    value decEcPoint = deencode(ecPoint);
-    //print(decEcPoint);
-    
-    value params = X9ECParameters(sharedPart.domain);
-    value decParams = deencode(params);
-    value decSeqParams = toASN1Sequence(decParams);
-    //print(decSeqParams);
-    
-    value algorithmIdentifier = AlgorithmIdentifier(identifier, params);
-    value decAlgorithmIdentifier = deencode(algorithmIdentifier);
-    value decSeqAlgorithmIdentifier = toASN1Sequence(decAlgorithmIdentifier);
-    //print(decSeqAlgorithmIdentifier);
-    
-    value encodedPoint = encodePoint(sharedPart.q);
-    value keyData = BitString(encodedPoint);
-    //print(Size(encodedPoint.size).encoded);
-    //print(encodedPoint);
-    //print(keyData.encoded);
-    value decKeyData = deencode(keyData);
-    //print(decKeyData);
-    
-    value bitString = BitString([0]);
-    value testSeq = ASN1Sequence([bitString, identifier]);
-    //print("Size[1]: " + Size(1).encoded.string);
-    //print("BitString: " + bitString.encoded.string);
-    //print("Identifier: " + identifier.encoded.string);
-    //print("TestSeq: " + testSeq.asn1.encoded.string);
-    value decTestSeq = deencode(testSeq);
-    value decSeqTestSeq = toASN1Sequence(decTestSeq);
-    //print(decSeqTestSeq);
-    
-    value publicKey = SubjectPublicKeyInfo(algorithmIdentifier, encodePoint(sharedPart.q));
-    value decPublicKey = deencode(publicKey);
-    value decSeqPublicKey = toASN1Sequence(decPublicKey);
-    //print(decSeqPublicKey);
-}
-
-void decodeJavaSharedPart(EphemeralSharedPart sharedPart) {
-    assert(is ASN1Sequence keySeq = sharedPart.asn1.sequence[1]);
-    value encodedKey = arrays.toByteArray(keySeq.encoded);
-    
-    value subPubKeyInfo = toSubjectPublicKeyInfo(encodedKey);
-    assert(subPubKeyInfo.algorithm.algorithm == id_ecPublicKey);
-    assert(subPubKeyInfo.publicKeyData.bytes.array.sequence == encodePoint(sharedPart.q));
-    
-    value encodedKeySpec = X509EncodedKeySpec(encodedKey);
-    fetchKeyFactory("ECDH").generatePublic(encodedKeySpec);
-}
-
-void encodeSharedPart() {
-    EphemeralSharedPart sharedPart = IncompleteEphemeralKey().sharedPart;
-
-    deencodeParts(sharedPart);
-    decodeJavaSharedPart(sharedPart);
-    
-    value encodedSharedPart = sharedPart.encoded;
-    EphemeralSharedPart decodedPart = decodeSharedPart(encodedSharedPart);
-    assert(decodedPart == sharedPart);
-}
-
-void aes(SharedKey key) {
-    String message = "Hello world!";
-    CipherText enc = encrypt(key, arrays.toByteArray(codepointsOf(message)));
-    
-    ByteArray dec = decrypt(key, enc);
-    String fin = string(asCodepoints(dec.array));
-    assert(fin == message);
-    print(fin);
+    util();
+    math();
+    asn1();
+    cms();
+    primitives();
+    root();
 }

File test/util.ceylon

+import toycrypto.util { forever }
+
+doc "Run the tests for the package `toycrypto.util`."
+void util() {
+    forever(() => 1).find((Integer elem) => true);
+}