mirror of
https://github.com/corda/corda.git
synced 2025-06-17 06:38:21 +00:00
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:
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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].
|
||||
|
@ -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"))
|
@ -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)
|
||||
|
||||
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
@ -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)
|
||||
|
Reference in New Issue
Block a user