diff --git a/.idea/compiler.xml b/.idea/compiler.xml index f455e61871..f0b65c5a84 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -93,6 +93,7 @@ + diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index ab6ea15722..605b95466c 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.internal.X509EdDSAEngine import net.corda.core.serialization.serialize import net.i2p.crypto.eddsa.* import net.i2p.crypto.eddsa.math.GroupElement diff --git a/core/src/main/kotlin/net/i2p/crypto/eddsa/X509EdDSAEngine.kt b/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt similarity index 67% rename from core/src/main/kotlin/net/i2p/crypto/eddsa/X509EdDSAEngine.kt rename to core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt index 9ce017a484..0bebc73130 100644 --- a/core/src/main/kotlin/net/i2p/crypto/eddsa/X509EdDSAEngine.kt +++ b/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt @@ -1,5 +1,7 @@ -package net.i2p.crypto.eddsa +package net.corda.core.internal +import net.i2p.crypto.eddsa.EdDSAEngine +import net.i2p.crypto.eddsa.EdDSAPublicKey import java.security.* import java.security.spec.AlgorithmParameterSpec import java.security.spec.X509EncodedKeySpec @@ -16,33 +18,34 @@ class X509EdDSAEngine : Signature { constructor() : super(EdDSAEngine.SIGNATURE_ALGORITHM) { engine = EdDSAEngine() } + constructor(digest: MessageDigest) : super(EdDSAEngine.SIGNATURE_ALGORITHM) { engine = EdDSAEngine(digest) } - override fun engineInitSign(privateKey: PrivateKey) = engine.engineInitSign(privateKey) + override fun engineInitSign(privateKey: PrivateKey) = engine.initSign(privateKey) + override fun engineInitSign(privateKey: PrivateKey, random: SecureRandom) = engine.initSign(privateKey, random) + override fun engineInitVerify(publicKey: PublicKey) { val parsedKey = if (publicKey is sun.security.x509.X509Key) { EdDSAPublicKey(X509EncodedKeySpec(publicKey.encoded)) } else { publicKey } - engine.engineInitVerify(parsedKey) + + engine.initVerify(parsedKey) } - override fun engineVerify(sigBytes: ByteArray): Boolean = engine.engineVerify(sigBytes) - override fun engineSign(): ByteArray = engine.engineSign() - override fun engineUpdate(b: Byte) = engine.engineUpdate(b) - override fun engineUpdate(b: ByteArray, off: Int, len: Int) = engine.engineUpdate(b, off, len) - override fun engineGetParameters(): AlgorithmParameters { - val method = engine.javaClass.getMethod("engineGetParameters") - return method.invoke(engine) as AlgorithmParameters - } + override fun engineSign(): ByteArray = engine.sign() + override fun engineVerify(sigBytes: ByteArray): Boolean = engine.verify(sigBytes) + + override fun engineUpdate(b: Byte) = engine.update(b) + override fun engineUpdate(b: ByteArray, off: Int, len: Int) = engine.update(b, off, len) + + override fun engineGetParameters(): AlgorithmParameters = engine.parameters override fun engineSetParameter(params: AlgorithmParameterSpec) = engine.setParameter(params) - override fun engineGetParameter(param: String): Any = engine.engineGetParameter(param) - override fun engineSetParameter(param: String, value: Any?) = engine.engineSetParameter(param, value) - override fun engineInitSign(privateKey: PrivateKey, random: SecureRandom) { - val method = engine.javaClass.getMethod("engineInitSign", PrivateKey::class.java, SecureRandom::class.java) - method.invoke(engine, privateKey, random) - } + @Suppress("DEPRECATION") + override fun engineGetParameter(param: String): Any = engine.getParameter(param) + @Suppress("DEPRECATION") + override fun engineSetParameter(param: String, value: Any?) = engine.setParameter(param, value) } diff --git a/core/src/test/kotlin/net/corda/core/internal/X509EdDSAEngineTest.kt b/core/src/test/kotlin/net/corda/core/internal/X509EdDSAEngineTest.kt new file mode 100644 index 0000000000..3f79a6ce36 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/internal/X509EdDSAEngineTest.kt @@ -0,0 +1,117 @@ +package net.corda.core.internal + +import net.corda.core.crypto.Crypto +import net.i2p.crypto.eddsa.EdDSAEngine +import net.i2p.crypto.eddsa.EdDSAPublicKey +import org.junit.Test +import sun.security.util.BitArray +import sun.security.util.ObjectIdentifier +import sun.security.x509.AlgorithmId +import sun.security.x509.X509Key +import java.math.BigInteger +import java.security.InvalidKeyException +import java.util.* +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class TestX509Key(algorithmId: AlgorithmId, key: BitArray) : X509Key() { + init { + this.algid = algorithmId + this.setKey(key) + this.encode() + } +} + +class X509EdDSAEngineTest { + companion object { + private const val SEED = 20170920L + private const val TEST_DATA_SIZE = 2000 + + // offset into an EdDSA header indicating where the key header and actual key start + // in the underlying byte array + private const val keyHeaderStart = 9 + private const val keyStart = 12 + + private fun toX509Key(publicKey: EdDSAPublicKey): X509Key { + val internals = publicKey.encoded + + // key size in the header includes the count unused bits at the end of the key + // [keyHeaderStart + 2] but NOT the key header ID [keyHeaderStart] so the + // actual length of the key blob is size - 1 + val keySize = (internals[keyHeaderStart + 1].toInt()) - 1 + + val key = ByteArray(keySize) + System.arraycopy(internals, keyStart, key, 0, keySize) + + // 1.3.101.102 is the EdDSA OID + return TestX509Key(AlgorithmId(ObjectIdentifier("1.3.101.112")), BitArray(keySize * 8, key)) + } + } + + /** + * Put the X509EdDSA engine through basic tests to verify that the functions are hooked up correctly. + */ + @Test + fun `sign and verify`() { + val engine = X509EdDSAEngine() + val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED)) + val publicKey = keyPair.public as EdDSAPublicKey + val randomBytes = ByteArray(TEST_DATA_SIZE) + Random(SEED).nextBytes(randomBytes) + engine.initSign(keyPair.private) + engine.update(randomBytes[0]) + engine.update(randomBytes, 1, randomBytes.size - 1) + + // Now verify the signature + val signature = engine.sign() + + engine.initVerify(publicKey) + engine.update(randomBytes) + assertTrue { engine.verify(signature) } + } + + /** + * Verify that signing with an X509Key wrapped EdDSA key works. + */ + @Test + fun `sign and verify with X509Key`() { + val engine = X509EdDSAEngine() + val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED + 1)) + val publicKey = toX509Key(keyPair.public as EdDSAPublicKey) + val randomBytes = ByteArray(TEST_DATA_SIZE) + Random(SEED + 1).nextBytes(randomBytes) + engine.initSign(keyPair.private) + engine.update(randomBytes[0]) + engine.update(randomBytes, 1, randomBytes.size - 1) + + // Now verify the signature + val signature = engine.sign() + + engine.initVerify(publicKey) + engine.update(randomBytes) + assertTrue { engine.verify(signature) } + } + + /** + * Verify that signing with an X509Key wrapped EdDSA key fails when using the underlying EdDSAEngine. + */ + @Test + fun `sign and verify with X509Key and old engine fails`() { + val engine = EdDSAEngine() + val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED + 1)) + val publicKey = toX509Key(keyPair.public as EdDSAPublicKey) + val randomBytes = ByteArray(TEST_DATA_SIZE) + Random(SEED + 1).nextBytes(randomBytes) + engine.initSign(keyPair.private) + engine.update(randomBytes[0]) + engine.update(randomBytes, 1, randomBytes.size - 1) + + // Now verify the signature + val signature = engine.sign() + assertFailsWith { + engine.initVerify(publicKey) + engine.update(randomBytes) + engine.verify(signature) + } + } +} \ No newline at end of file