ENT-11101: Fix all crypto issues introduced by Java 17 upgrade

The various crypto tests that were previously ignored have been re-enabled.

The abandoned i2p EdDSA library has been replaced with native support that was added in Java 15.

Java 17 (via the `SunEC` provider) does not support the secp256k1 curve (one of the two ECDSA curves supported in Corda). This would not normally have been an issue as secp256k1 is already taken care of by Bouncy Castle. However, this only works if the `Crypto` API is used or if `”BC”` is explicitly specified as the provider (e.g. `Signature.getInstance(“SHA256withECDSA”, “BC”)`). If no provider is specified, which is what is more common, and actually what the Java docs recommend, then this doesn’t work as the `SunEC` provider is selected. To resolve this, a custom provider was created, installed just in front of `SunEC`, which “augments” `SunEC` by delegating to Bouncy Castle if keys or parameters for secp256k1 are encountered.

`X509Utilities.createCertificate` now calls `X509Certificate.verify()` to verify the created certificate, rather than using the Bouncy Castle API. This is more representative of how certificates will be verified (e.g. during SSL handshake) and weeds out other issues (such as unsupported curve error for secp256k1).

`BCCryptoService` has been renamed to `DefaultCryptoService` as it no longer explicitly uses Bouncy Castle but rather uses the installed security providers. This was done to fix a failing test. Further, `BCCryptoService` was already relying on the installed providers in some places.

The hack to get Corda `SecureRandom` working was also resolved. Also, as an added bonus, tests which ignored `SPHINCS256_SHA256` have been reinstated.

Note, there is a slightly inconsistency between how EdDSA and ECDSA keys are handled (and also RSA). For the later, Bouncy Castle is preferred, and methods such as `toSupportedKey*` will convert any JDK class to Bouncy Castle. For EdDSA the preference is the JDK (`SunEC`). However, this is simply a continuation of the previous preference of the i2p library over Bouncy Castle.
This commit is contained in:
Shams Asari
2024-02-29 14:46:27 +00:00
parent 6dfbed572e
commit 0091807c2f
45 changed files with 507 additions and 648 deletions

View File

@ -78,7 +78,7 @@ interface CertificateStore : Iterable<Pair<String, X509Certificate>> {
}
fun setCertPathOnly(alias: String, certificates: List<X509Certificate>) {
// In case CryptoService and CertificateStore share the same KeyStore (i.e., when BCCryptoService is used),
// In case CryptoService and CertificateStore share the same KeyStore (i.e., when DefaultCryptoService is used),
// extract the existing key from the Keystore and store it again along with the new certificate chain.
// This is because KeyStores do not support updateKeyEntry and thus we cannot update the certificate chain
// without overriding the key entry.

View File

@ -1,6 +1,5 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto.SPHINCS256_SHA256
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.internal.Instances
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
@ -27,9 +26,7 @@ object ContentSignerBuilder {
val sig = try {
signatureInstance.apply {
// TODO special handling for Sphincs due to a known BouncyCastle's Sphincs bug we reported.
// It is fixed in BC 161b12, so consider updating the below if-statement after updating BouncyCastle.
if (random != null && signatureScheme != SPHINCS256_SHA256) {
if (random != null) {
initSign(privateKey, random)
} else {
initSign(privateKey)
@ -48,7 +45,7 @@ object ContentSignerBuilder {
private class SignatureOutputStream(private val sig: Signature, private val optimised: Boolean) : OutputStream() {
private var alreadySigned = false
internal val signature: ByteArray by lazy {
val signature: ByteArray by lazy {
try {
alreadySigned = true
sig.sign()

View File

@ -69,7 +69,7 @@ import kotlin.io.path.reader
import kotlin.io.path.writer
object X509Utilities {
// Note that this default value only applies to BCCryptoService. Other implementations of CryptoService may have to use different
// Note that this default value only applies to DefaultCryptoService. Other implementations of CryptoService may have to use different
// schemes (for instance `UtimacoCryptoService.DEFAULT_IDENTITY_SIGNATURE_SCHEME`).
val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
@ -303,10 +303,10 @@ object X509Utilities {
crlDistPoint: String? = null,
crlIssuer: X500Name? = null): X509Certificate {
val builder = createPartialCertificate(certificateType, issuer, issuerPublicKey, subject, subjectPublicKey, validityWindow, nameConstraints, crlDistPoint, crlIssuer)
return builder.build(issuerSigner).run {
require(isValidOn(Date())){"Certificate is not valid at instant now"}
toJca()
}
val certificate = builder.build(issuerSigner).toJca()
certificate.checkValidity(Date())
certificate.verify(issuerPublicKey)
return certificate
}
/**
@ -340,18 +340,22 @@ object X509Utilities {
validityWindow,
nameConstraints,
crlDistPoint,
crlIssuer)
return builder.build(signer).run {
require(isValidOn(Date())){"Certificate is not valid at instant now"}
require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public))){"Invalid signature"}
toJca()
}
crlIssuer
)
val certificate = builder.build(signer).toJca()
certificate.checkValidity(Date())
certificate.verify(issuerKeyPair.public)
return certificate
}
/**
* Create certificate signing request using provided information.
*/
fun createCertificateSigningRequest(subject: X500Principal, email: String, publicKey: PublicKey, contentSigner: ContentSigner, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest {
fun createCertificateSigningRequest(subject: X500Principal,
email: String,
publicKey: PublicKey,
contentSigner: ContentSigner,
certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest {
return JcaPKCS10CertificationRequestBuilder(subject, publicKey)
.addAttribute(BCStyle.E, DERUTF8String(email))
.addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole)
@ -410,7 +414,7 @@ object X509Utilities {
}
// Assuming cert type to role is 1:1
val CertRole.certificateType: CertificateType get() = CertificateType.values().first { it.role == this }
val CertRole.certificateType: CertificateType get() = CertificateType.entries.first { it.role == this }
/**
* Convert a [X509Certificate] into BouncyCastle's [X509CertificateHolder].

View File

@ -1,9 +1,8 @@
package net.corda.nodeapi.internal.cryptoservice.bouncycastle
package net.corda.nodeapi.internal.cryptoservice
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.internal.Instances.getSignatureInstance
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
import net.corda.core.crypto.internal.Instances.withSignature
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sha256
import net.corda.core.utilities.detailedLogger
@ -14,27 +13,30 @@ import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.nodeapi.internal.crypto.save
import net.corda.nodeapi.internal.cryptoservice.*
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
import org.bouncycastle.operator.ContentSigner
import java.nio.file.Path
import java.security.*
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.PrivateKey
import java.security.Provider
import java.security.PublicKey
import java.security.Signature
import java.security.spec.ECGenParameterSpec
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.security.auth.x500.X500Principal
/**
* Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
* Basic implementation of a [CryptoService] which uses Corda's [Provider]s for cryptographic operations
* and a Java KeyStore in the form of [CertificateStore] to store private keys.
* This service reuses the [NodeConfiguration.signingCertificateStore] to store keys.
*
* The [wrappingKeyStorePath] must be provided in order to execute any wrapping operations (e.g. [createWrappingKey], [generateWrappedKeyPair])
*/
class BCCryptoService(private val legalName: X500Principal,
private val certificateStoreSupplier: CertificateStoreSupplier,
private val wrappingKeyStorePath: Path? = null) : CryptoService {
@Suppress("TooManyFunctions")
class DefaultCryptoService(private val legalName: X500Principal,
private val certificateStoreSupplier: CertificateStoreSupplier,
private val wrappingKeyStorePath: Path? = null) : CryptoService {
private companion object {
val detailedLogger = detailedLogger()
@ -97,7 +99,7 @@ class BCCryptoService(private val legalName: X500Principal,
private fun signWithAlgorithm(alias: String, data: ByteArray, signAlgorithm: String): ByteArray {
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
val signature = Signature.getInstance(signAlgorithm, cordaBouncyCastleProvider)
val signature = Signature.getInstance(signAlgorithm)
detailedLogger.trace { "CryptoService(action=signing_start;alias=$alias;algorithm=$signAlgorithm)" }
signature.initSign(privateKey, newSecureRandom())
signature.update(data)
@ -126,7 +128,7 @@ class BCCryptoService(private val legalName: X500Principal,
/**
* If a node is running in [NodeConfiguration.devMode] and for backwards compatibility purposes, the same [KeyStore]
* is reused outside [BCCryptoService] to update certificate paths. [resyncKeystore] will sync [BCCryptoService]'s
* is reused outside [DefaultCryptoService] to update certificate paths. [resyncKeystore] will sync [DefaultCryptoService]'s
* loaded [certificateStore] in memory with the contents of the corresponding [KeyStore] file.
*/
fun resyncKeystore() {
@ -178,7 +180,7 @@ class BCCryptoService(private val legalName: X500Principal,
}
val wrappingKey = wrappingKeyStore.getKey(masterKeyAlias, certificateStore.entryPassword.toCharArray())
val cipher = Cipher.getInstance("AESWRAPPAD", cordaBouncyCastleProvider)
val cipher = Cipher.getInstance("AESWRAPPAD")
cipher.init(Cipher.WRAP_MODE, wrappingKey)
val keyPairGenerator = keyPairGeneratorFromScheme(childKeyScheme)
@ -199,22 +201,23 @@ class BCCryptoService(private val legalName: X500Principal,
1 -> "AESWRAPPAD"
else -> "AES"
}
val cipher = Cipher.getInstance(algorithm, cordaBouncyCastleProvider)
val cipher = Cipher.getInstance(algorithm)
cipher.init(Cipher.UNWRAP_MODE, wrappingKey)
val privateKey = cipher.unwrap(wrappedPrivateKey.keyMaterial, keyAlgorithmFromScheme(wrappedPrivateKey.signatureScheme), Cipher.PRIVATE_KEY) as PrivateKey
val signature = getSignatureInstance(wrappedPrivateKey.signatureScheme.signatureName, cordaBouncyCastleProvider)
signature.initSign(privateKey, newSecureRandom())
signature.update(payloadToSign)
return signature.sign()
return withSignature(wrappedPrivateKey.signatureScheme) { signature ->
signature.initSign(privateKey, newSecureRandom())
signature.update(payloadToSign)
signature.sign()
}
}
override fun getWrappingMode(): WrappingMode? = WrappingMode.DEGRADED_WRAPPED
override fun getWrappingMode(): WrappingMode = WrappingMode.DEGRADED_WRAPPED
private fun keyPairGeneratorFromScheme(scheme: SignatureScheme): KeyPairGenerator {
val algorithm = keyAlgorithmFromScheme(scheme)
val keyPairGenerator = KeyPairGenerator.getInstance(algorithm, cordaBouncyCastleProvider)
val keyPairGenerator = KeyPairGenerator.getInstance(algorithm)
when (scheme) {
Crypto.ECDSA_SECP256R1_SHA256 -> keyPairGenerator.initialize(ECGenParameterSpec("secp256r1"))
Crypto.ECDSA_SECP256K1_SHA256 -> keyPairGenerator.initialize(ECGenParameterSpec("secp256k1"))

View File

@ -20,7 +20,6 @@ import de.javakaffee.kryoserializers.guava.ImmutableSortedSetSerializer
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.AbstractAttachment
@ -40,14 +39,6 @@ import net.corda.core.utilities.toNonEmptySet
import net.corda.serialization.internal.DefaultWhitelist
import net.corda.serialization.internal.GeneratedAttachment
import net.corda.serialization.internal.MutableClassWhitelist
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.objenesis.instantiator.ObjectInstantiator
import org.objenesis.strategy.InstantiatorStrategy
import org.objenesis.strategy.StdInstantiatorStrategy
@ -62,14 +53,13 @@ import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.util.*
import kotlin.collections.ArrayList
object DefaultKryoCustomizer {
private val serializationWhitelists: List<SerializationWhitelist> by lazy {
ServiceLoader.load(SerializationWhitelist::class.java, this.javaClass.classLoader).toList() + DefaultWhitelist
}
fun customize(kryo: Kryo, publicKeySerializer: Serializer<PublicKey> = PublicKeySerializer): Kryo {
fun customize(kryo: Kryo): Kryo {
return kryo.apply {
isRegistrationRequired = false
references = true
@ -110,6 +100,8 @@ object DefaultKryoCustomizer {
// Please add any new registrations to the end.
addDefaultSerializer(LinkedHashMapIteratorSerializer.getIterator()::class.java.superclass, LinkedHashMapIteratorSerializer)
addDefaultSerializer(PublicKey::class.java, PublicKeySerializer)
addDefaultSerializer(PrivateKey::class.java, PrivateKeySerializer)
register(LinkedHashMapEntrySerializer.getEntry()::class.java, LinkedHashMapEntrySerializer)
register(LinkedListItrSerializer.getListItr()::class.java, LinkedListItrSerializer)
register(Arrays.asList("").javaClass, ArraysAsListSerializer())
@ -126,11 +118,6 @@ object DefaultKryoCustomizer {
// InputStream subclasses whitelisting, required for attachments.
register(BufferedInputStream::class.java, InputStreamSerializer)
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
register(PublicKey::class.java, publicKeySerializer)
register(PrivateKey::class.java, PrivateKeySerializer)
register(EdDSAPublicKey::class.java, publicKeySerializer)
register(EdDSAPrivateKey::class.java, PrivateKeySerializer)
register(CompositeKey::class.java, publicKeySerializer) // Using a custom serializer for compactness
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
register(Array<StackTraceElement>::class, read = { _, _ -> emptyArray() }, write = { _, _, _ -> })
// This ensures a NonEmptySetSerializer is constructed with an initial value.
@ -139,12 +126,6 @@ object DefaultKryoCustomizer {
register(Class::class.java, ClassSerializer)
register(FileInputStream::class.java, InputStreamSerializer)
register(CertPath::class.java, CertPathSerializer)
register(BCECPrivateKey::class.java, PrivateKeySerializer)
register(BCECPublicKey::class.java, publicKeySerializer)
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
register(BCRSAPublicKey::class.java, publicKeySerializer)
register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer)
register(BCSphincs256PublicKey::class.java, publicKeySerializer)
register(NotaryChangeWireTransaction::class.java, NotaryChangeWireTransactionSerializer)
register(PartyAndCertificate::class.java, PartyAndCertificateSerializer)

View File

@ -28,7 +28,6 @@ import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.SgxSupport
import net.corda.serialization.internal.serializationContextKey
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@ -83,11 +82,8 @@ class ImmutableClassSerializer<T : Any>(val klass: KClass<T>) : Serializer<T>()
init {
// Verify that this class is immutable (all properties are final).
// We disable this check inside SGX as the reflection blows up.
if (!SgxSupport.isInsideEnclave) {
props.forEach {
require(it !is KMutableProperty<*>) { "$it mutable property of class: ${klass} is unsupported" }
}
props.forEach {
require(it !is KMutableProperty<*>) { "$it mutable property of class: $klass is unsupported" }
}
}

View File

@ -6,16 +6,15 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.coretesting.internal.TestNodeInfoBuilder
import net.corda.coretesting.internal.signWith
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.coretesting.internal.TestNodeInfoBuilder
import net.corda.coretesting.internal.signWith
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import java.security.KeyPair
@ -56,7 +55,6 @@ class SignedNodeInfoTest {
}
@Test(timeout=300_000)
@Ignore("TODO JDK17: Fixme")
fun `verifying composite keys only`() {
val aliceKeyPair = generateKeyPair()
val bobKeyPair = generateKeyPair()

View File

@ -28,6 +28,6 @@ class ContentSignerBuilderTest {
.isThrownBy {
ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
}
.withMessage("Incorrect key type EC for signature scheme NONEwithEdDSA")
.withMessage("Incorrect key type EC for signature scheme Ed25519")
}
}
}

View File

@ -1,33 +1,32 @@
package net.corda.nodeapi.internal.cryptoservice.bouncycastle
package net.corda.nodeapi.internal.cryptoservice
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
import net.corda.core.utilities.days
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
import net.corda.nodeapi.internal.cryptoservice.WrappedPrivateKey
import net.corda.nodeapi.internal.cryptoservice.WrappingMode
import net.corda.testing.core.ALICE_NAME
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.testing.core.ALICE_NAME
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.FileOutputStream
import java.nio.file.Path
import java.security.*
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.PublicKey
import java.security.Signature
import java.security.spec.ECGenParameterSpec
import java.time.Duration
import java.util.*
import java.util.UUID
import javax.crypto.Cipher
import javax.security.auth.x500.X500Principal
import kotlin.io.path.div
@ -35,7 +34,7 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class BCCryptoServiceTests {
class DefaultCryptoServiceTests {
companion object {
val clearData = "data".toByteArray()
}
@ -61,15 +60,13 @@ class BCCryptoServiceTests {
}
@Test(timeout=300_000)
@Ignore("TODO JDK17: Fixme")
fun `BCCryptoService generate key pair and sign both data and cert`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
fun `cryptoService generate key pair and sign both data and cert`() {
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
// Testing every supported scheme.
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY
&& it.signatureName != "SHA512WITHSPHINCS256"}.forEach { generateKeyAndSignForScheme(cryptoService, it) }
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach { generateKeyAndSignForScheme(cryptoService, it) }
}
private fun generateKeyAndSignForScheme(cryptoService: BCCryptoService, signatureScheme: SignatureScheme) {
private fun generateKeyAndSignForScheme(cryptoService: DefaultCryptoService, signatureScheme: SignatureScheme) {
val alias = "signature${signatureScheme.schemeNumberID}"
val pubKey = cryptoService.generateKeyPair(alias, signatureScheme)
assertTrue { cryptoService.containsKey(alias) }
@ -95,12 +92,10 @@ class BCCryptoServiceTests {
}
@Test(timeout=300_000)
@Ignore("TODO JDK17: Fixme")
fun `BCCryptoService generate key pair and sign with existing schemes`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
fun `cryptoService generate key pair and sign with existing schemes`() {
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
// Testing every supported scheme.
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY
&& it.signatureName != "SHA512WITHSPHINCS256"}.forEach {
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach {
val alias = "signature${it.schemeNumberID}"
val pubKey = cryptoService.generateKeyPair(alias, it)
assertTrue { cryptoService.containsKey(alias) }
@ -110,9 +105,7 @@ class BCCryptoServiceTests {
}
@Test(timeout=300_000)
@Ignore("TODO JDK17: Fixme")
fun `BCCryptoService generate key pair and sign with passed signing algorithm`() {
fun `cryptoService generate key pair and sign with passed signing algorithm`() {
assertTrue{signAndVerify(signAlgo = "NONEwithRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")}
assertTrue{signAndVerify(signAlgo = "MD2withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")}
assertTrue{signAndVerify(signAlgo = "MD5withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")}
@ -132,7 +125,7 @@ class BCCryptoServiceTests {
private fun signAndVerify(signAlgo: String, alias: String, keyTypeAlgo: String): Boolean {
val keyPairGenerator = KeyPairGenerator.getInstance(keyTypeAlgo)
val keyPair = keyPairGenerator.genKeyPair()
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, createKeystore(alias, keyPair), wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, createKeystore(alias, keyPair), wrappingKeyStorePath)
assertTrue { cryptoService.containsKey(alias) }
val signatureData = cryptoService.sign(alias, clearData, signAlgo)
return verify(signAlgo, cryptoService.getPublicKey(alias), signatureData, clearData)
@ -175,7 +168,7 @@ class BCCryptoServiceTests {
@Test(timeout=300_000)
fun `When key does not exist getPublicKey, sign and getSigner should throw`() {
val nonExistingAlias = "nonExistingAlias"
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
assertFalse { cryptoService.containsKey(nonExistingAlias) }
assertFailsWith<CryptoServiceException> { cryptoService.getPublicKey(nonExistingAlias) }
assertFailsWith<CryptoServiceException> { cryptoService.sign(nonExistingAlias, clearData) }
@ -184,7 +177,7 @@ class BCCryptoServiceTests {
@Test(timeout=300_000)
fun `cryptoService supports degraded mode of wrapping`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val supportedMode = cryptoService.getWrappingMode()
assertThat(supportedMode).isEqualTo(WrappingMode.DEGRADED_WRAPPED)
@ -192,7 +185,7 @@ class BCCryptoServiceTests {
@Test(timeout=300_000)
fun `cryptoService does not fail when requested to create same wrapping key twice with failIfExists is false`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val keyAlias = UUID.randomUUID().toString()
cryptoService.createWrappingKey(keyAlias)
@ -201,7 +194,7 @@ class BCCryptoServiceTests {
@Test(timeout=300_000)
fun `cryptoService does fail when requested to create same wrapping key twice with failIfExists is true`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val keyAlias = UUID.randomUUID().toString()
cryptoService.createWrappingKey(keyAlias)
@ -213,7 +206,7 @@ class BCCryptoServiceTests {
@Test(timeout=300_000)
fun `cryptoService fails when asked to generate wrapped key pair or sign, but the master key specified does not exist`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val wrappingKeyAlias = UUID.randomUUID().toString()
@ -230,7 +223,7 @@ class BCCryptoServiceTests {
@Test(timeout=300_000)
fun `cryptoService can generate wrapped key pair and sign with the private key successfully, using default algorithm`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val wrappingKeyAlias = UUID.randomUUID().toString()
cryptoService.createWrappingKey(wrappingKeyAlias)
@ -239,7 +232,7 @@ class BCCryptoServiceTests {
@Test(timeout=300_000)
fun `cryptoService can generate wrapped key pair and sign with the private key successfully`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val wrappingKeyAlias = UUID.randomUUID().toString()
cryptoService.createWrappingKey(wrappingKeyAlias)
@ -264,7 +257,7 @@ class BCCryptoServiceTests {
@Test(timeout=300_000)
fun `cryptoService can sign with previously encoded version of wrapped key`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val wrappingKeyAlias = UUID.randomUUID().toString()
cryptoService.createWrappingKey(wrappingKeyAlias)

View File

@ -139,7 +139,12 @@ class KryoTests(private val compression: CordaSerializationEncoding?) {
@Test(timeout=300_000)
fun `deserialised key pair functions the same as serialised one`() {
val keyPair = generateKeyPair()
// The default signature scheme, EDDSA_ED25519_SHA512, generates public keys which have a writeReplace method (EdDSAPublicKeyImpl).
// This is picked up by Quasar's custom ReplaceableObjectKryo, which will *always* use the writeReplace for serialisation, ignoring
// any Kryo serialisers that might have been configured. This thus means the deserialisation path does not go via
// Cryto.decodePublicKey, which interns the materialised PublicKey. To avoid all of this, and test the last assertion, we use
// ECDSA keys, whose implementation doesn't have a writeReplace method.
val keyPair = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val bitsToSign: ByteArray = Ints.toByteArray(0x01234567)
val wrongBits: ByteArray = Ints.toByteArray(0x76543210)
val signature = keyPair.sign(bitsToSign)