mirror of
https://github.com/corda/corda.git
synced 2025-02-06 19:19:19 +00:00
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:
parent
1d6bd85f8a
commit
49a70cdbd6
@ -1,10 +1,7 @@
|
|||||||
package net.corda.core.crypto
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
import net.i2p.crypto.eddsa.*
|
||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
|
||||||
import net.i2p.crypto.eddsa.EdDSASecurityProvider
|
|
||||||
import net.i2p.crypto.eddsa.math.GroupElement
|
import net.i2p.crypto.eddsa.math.GroupElement
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
|
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
|
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 org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.*
|
import java.security.*
|
||||||
|
import java.security.KeyFactory
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
import java.security.spec.InvalidKeySpecException
|
import java.security.spec.InvalidKeySpecException
|
||||||
import java.security.spec.PKCS8EncodedKeySpec
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
import java.security.spec.X509EncodedKeySpec
|
import java.security.spec.X509EncodedKeySpec
|
||||||
@ -200,6 +199,8 @@ object Crypto {
|
|||||||
|
|
||||||
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
|
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
|
||||||
putAll(EdDSASecurityProvider())
|
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))
|
addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID.algorithm, KeyInfoConverter(EDDSA_ED25519_SHA512))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
48
core/src/main/kotlin/net/i2p/crypto/eddsa/X509EdDSAEngine.kt
Normal file
48
core/src/main/kotlin/net/i2p/crypto/eddsa/X509EdDSAEngine.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,18 @@
|
|||||||
package net.corda.core.identity
|
package net.corda.core.identity
|
||||||
|
|
||||||
import net.corda.core.crypto.entropyToKeyPair
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
|
import net.corda.core.internal.read
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
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.getTestPartyAndCertificate
|
||||||
import net.corda.testing.withTestSerialization
|
import net.corda.testing.withTestSerialization
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
import java.security.KeyStore
|
||||||
|
|
||||||
class PartyAndCertificateTest {
|
class PartyAndCertificateTest {
|
||||||
@Test
|
@Test
|
||||||
@ -22,4 +27,26 @@ class PartyAndCertificateTest {
|
|||||||
assertThat(copy.certificate).isEqualTo(original.certificate)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user