CORDA-579: Add EdDSA engine that understands X.509 keys (#1540)

* Add EdDSA engine that understands X.509 keys

* Add test for Certificate serialization

* Address PR comments from Kostas
This commit is contained in:
Ross Nicoll 2017-09-19 17:50:04 +01:00 committed by josecoll
parent 1d6bd85f8a
commit 49a70cdbd6
3 changed files with 80 additions and 4 deletions

View File

@ -1,10 +1,7 @@
package net.corda.core.crypto
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.EdDSASecurityProvider
import net.i2p.crypto.eddsa.*
import net.i2p.crypto.eddsa.math.GroupElement
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
@ -41,6 +38,8 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
import java.math.BigInteger
import java.security.*
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
@ -200,6 +199,8 @@ object Crypto {
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
putAll(EdDSASecurityProvider())
// Override the normal EdDSA engine with one which can handle X509 keys.
put("Signature.${EdDSAEngine.SIGNATURE_ALGORITHM}", X509EdDSAEngine::class.qualifiedName)
addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID.algorithm, KeyInfoConverter(EDDSA_ED25519_SHA512))
}

View File

@ -0,0 +1,48 @@
package net.i2p.crypto.eddsa
import java.security.*
import java.security.spec.AlgorithmParameterSpec
import java.security.spec.X509EncodedKeySpec
/**
* Wrapper around [EdDSAEngine] which can intelligently rewrite X509Keys to a [EdDSAPublicKey]. This is a temporary
* solution until this is integrated upstream and/or a custom certificate factory implemented to force the correct
* key type. Only intercepts public keys passed into [engineInitVerify], as there is no equivalent issue with private
* keys.
*/
class X509EdDSAEngine : Signature {
private val engine: EdDSAEngine
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 engineInitVerify(publicKey: PublicKey) {
val parsedKey = if (publicKey is sun.security.x509.X509Key) {
EdDSAPublicKey(X509EncodedKeySpec(publicKey.encoded))
} else {
publicKey
}
engine.engineInitVerify(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 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)
}
}

View File

@ -1,13 +1,18 @@
package net.corda.core.identity
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.internal.read
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.utilities.KEYSTORE_TYPE
import net.corda.node.utilities.save
import net.corda.testing.getTestPartyAndCertificate
import net.corda.testing.withTestSerialization
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.io.File
import java.math.BigInteger
import java.security.KeyStore
class PartyAndCertificateTest {
@Test
@ -22,4 +27,26 @@ class PartyAndCertificateTest {
assertThat(copy.certificate).isEqualTo(original.certificate)
}
}
@Test
fun `jdk serialization`() {
withTestSerialization {
val identity = getTestPartyAndCertificate(Party(
CordaX500Name(organisation = "Test Corp", locality = "Madrid", country = "ES"),
entropyToKeyPair(BigInteger.valueOf(83)).public))
val original = identity.certificate
val storePassword = "test"
val keyStoreFilePath = File.createTempFile("serialization_test", "jks").toPath()
var keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
keyStore.load(null, storePassword.toCharArray())
keyStore.setCertificateEntry(identity.name.toString(), original)
keyStore.save(keyStoreFilePath, storePassword)
// Load the key store back in again
keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
keyStoreFilePath.read { keyStore.load(it, storePassword.toCharArray()) }
val copy = keyStore.getCertificate(identity.name.toString())
assertThat(copy).isEqualTo(original) // .isNotSameAs(original)
}
}
}