diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/CryptoSignUtils.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/CryptoSignUtils.kt new file mode 100644 index 0000000000..9351d06bc3 --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/CryptoSignUtils.kt @@ -0,0 +1,68 @@ +@file:JvmName("CryptoSignUtils") + +package net.corda.deterministic.crypto + +import net.corda.core.crypto.* +import net.corda.core.crypto.Crypto.findSignatureScheme +import net.corda.core.crypto.Crypto.isSupportedSignatureScheme +import net.corda.core.serialization.serialize +import java.security.* + +/** + * This is a slightly modified copy of signing utils from net.corda.core.crypto.Crypto, which are normally removed from DJVM. + * However, we need those for TransactionSignatureTest. + */ +object CryptoSignUtils { + @JvmStatic + @Throws(InvalidKeyException::class, SignatureException::class) + fun doSign(schemeCodeName: String, privateKey: PrivateKey, clearData: ByteArray): ByteArray { + return doSign(findSignatureScheme(schemeCodeName), privateKey, clearData) + } + + /** + * Generic way to sign [ByteArray] data with a [PrivateKey] and a known [Signature]. + * @param signatureScheme a [SignatureScheme] object, retrieved from supported signature schemes, see [Crypto]. + * @param privateKey the signer's [PrivateKey]. + * @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root). + * @return the digital signature (in [ByteArray]) on the input message. + * @throws IllegalArgumentException if the signature scheme is not supported for this private key. + * @throws InvalidKeyException if the private key is invalid. + * @throws SignatureException if signing is not possible due to malformed data or private key. + */ + @JvmStatic + @Throws(InvalidKeyException::class, SignatureException::class) + fun doSign(signatureScheme: SignatureScheme, privateKey: PrivateKey, clearData: ByteArray): ByteArray { + require(isSupportedSignatureScheme(signatureScheme)) { + "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" + } + require(clearData.isNotEmpty()) { "Signing of an empty array is not permitted!" } + val signature = Signature.getInstance(signatureScheme.signatureName, signatureScheme.providerName) + signature.initSign(privateKey) + signature.update(clearData) + return signature.sign() + } + + /** + * Generic way to sign [SignableData] objects with a [PrivateKey]. + * [SignableData] is a wrapper over the transaction's id (Merkle root) in order to attach extra information, such as + * a timestamp or partial and blind signature indicators. + * @param keyPair the signer's [KeyPair]. + * @param signableData a [SignableData] object that adds extra information to a transaction. + * @return a [TransactionSignature] object than contains the output of a successful signing, signer's public key and + * the signature metadata. + * @throws IllegalArgumentException if the signature scheme is not supported for this private key. + * @throws InvalidKeyException if the private key is invalid. + * @throws SignatureException if signing is not possible due to malformed data or private key. + */ + @JvmStatic + @Throws(InvalidKeyException::class, SignatureException::class) + fun doSign(keyPair: KeyPair, signableData: SignableData): TransactionSignature { + val sigKey: SignatureScheme = findSignatureScheme(keyPair.private) + val sigMetaData: SignatureScheme = findSignatureScheme(keyPair.public) + require(sigKey == sigMetaData) { + "Metadata schemeCodeName: ${sigMetaData.schemeCodeName} is not aligned with the key type: ${sigKey.schemeCodeName}." + } + val signatureBytes = doSign(sigKey.schemeCodeName, keyPair.private, signableData.serialize().bytes) + return TransactionSignature(signatureBytes, keyPair.public, signableData.signatureMetadata) + } +} diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt index 4825d4787f..0e9191f308 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt @@ -37,7 +37,7 @@ class TransactionSignatureTest { // Sign the meta object. val transactionSignature: TransactionSignature = CheatingSecurityProvider().use { - keyPair.sign(signableData) + CryptoSignUtils.doSign(keyPair, signableData) } // Check auto-verification. @@ -52,7 +52,7 @@ class TransactionSignatureTest { fun `Signature metadata full failure clearData has changed`() { val signableData = SignableData(testBytes.sha256(), SignatureMetadata(1, Crypto.findSignatureScheme(keyPair.public).schemeNumberID)) val transactionSignature = CheatingSecurityProvider().use { - keyPair.sign(signableData) + CryptoSignUtils.doSign(keyPair, signableData) } Crypto.doVerify((testBytes + testBytes).sha256(), transactionSignature) } @@ -137,7 +137,8 @@ class TransactionSignatureTest { private fun signOneTx(txId: SecureHash, keyPair: KeyPair): TransactionSignature { val signableData = SignableData(txId, SignatureMetadata(3, Crypto.findSignatureScheme(keyPair.public).schemeNumberID)) return CheatingSecurityProvider().use { - keyPair.sign(signableData) + CryptoSignUtils.doSign(keyPair, signableData) + } } } 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 a172f31747..29465d2808 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -2,6 +2,7 @@ package net.corda.core.crypto import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM +import net.corda.core.StubOutForDJVM import net.corda.core.crypto.internal.* import net.corda.core.serialization.serialize import net.i2p.crypto.eddsa.EdDSAEngine @@ -23,6 +24,7 @@ import org.bouncycastle.asn1.sec.SECObjectIdentifiers import org.bouncycastle.asn1.x509.AlgorithmIdentifier import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.asn1.x9.X9ObjectIdentifiers +import org.bouncycastle.crypto.CryptoServicesRegistrar import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateKey @@ -381,6 +383,7 @@ object Crypto { * @throws InvalidKeyException if the private key is invalid. * @throws SignatureException if signing is not possible due to malformed data or private key. */ + @DeleteForDJVM @JvmStatic @Throws(InvalidKeyException::class, SignatureException::class) fun doSign(privateKey: PrivateKey, clearData: ByteArray): ByteArray = doSign(findSignatureScheme(privateKey), privateKey, clearData) @@ -395,6 +398,7 @@ object Crypto { * @throws InvalidKeyException if the private key is invalid. * @throws SignatureException if signing is not possible due to malformed data or private key. */ + @DeleteForDJVM @JvmStatic @Throws(InvalidKeyException::class, SignatureException::class) fun doSign(schemeCodeName: String, privateKey: PrivateKey, clearData: ByteArray): ByteArray { @@ -411,6 +415,7 @@ object Crypto { * @throws InvalidKeyException if the private key is invalid. * @throws SignatureException if signing is not possible due to malformed data or private key. */ + @DeleteForDJVM @JvmStatic @Throws(InvalidKeyException::class, SignatureException::class) fun doSign(signatureScheme: SignatureScheme, privateKey: PrivateKey, clearData: ByteArray): ByteArray { @@ -419,7 +424,16 @@ object Crypto { } require(clearData.isNotEmpty()) { "Signing of an empty array is not permitted!" } val signature = Signature.getInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName]) - signature.initSign(privateKey) + // Note that deterministic signature schemes, such as EdDSA, do not require extra randomness, but we have to + // ensure that non-deterministic algorithms (i.e., ECDSA) use non-blocking SecureRandom implementations (if possible). + // TODO consider updating this when the related BC issue for Sphincs is fixed. + if (signatureScheme != SPHINCS256_SHA256) { + signature.initSign(privateKey, newSecureRandom()) + } else { + // Special handling for Sphincs, due to a BC implementation issue. + // As Sphincs is deterministic, it does not require RNG input anyway. + signature.initSign(privateKey) + } signature.update(clearData) return signature.sign() } @@ -436,6 +450,7 @@ object Crypto { * @throws InvalidKeyException if the private key is invalid. * @throws SignatureException if signing is not possible due to malformed data or private key. */ + @DeleteForDJVM @JvmStatic @Throws(InvalidKeyException::class, SignatureException::class) fun doSign(keyPair: KeyPair, signableData: SignableData): TransactionSignature { @@ -1017,5 +1032,15 @@ object Crypto { @JvmStatic fun registerProviders() { providerMap + // Adding our non-blocking newSecureRandom as default for any BouncyCastle operations + // (applies only when a SecureRandom is not specifically defined, i.e., if we call + // signature.initSign(privateKey) instead of signature.initSign(privateKey, newSecureRandom() + // for a BC algorithm, i.e., ECDSA). + setBouncyCastleRNG() + } + + @StubOutForDJVM + private fun setBouncyCastleRNG() { + CryptoServicesRegistrar.setSecureRandom(newSecureRandom()) } } diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt index 1cc29b5fdd..535f0013ef 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -24,6 +24,7 @@ import java.security.* * @throws InvalidKeyException if the private key is invalid. * @throws SignatureException if signing is not possible due to malformed data or private key. */ +@DeleteForDJVM @Throws(InvalidKeyException::class, SignatureException::class) fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature(Crypto.doSign(this, bytesToSign)) @@ -36,6 +37,7 @@ fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature * @throws InvalidKeyException if the private key is invalid. * @throws SignatureException if signing is not possible due to malformed data or private key. */ +@DeleteForDJVM @Throws(InvalidKeyException::class, SignatureException::class) fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey { return DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes) @@ -49,10 +51,12 @@ fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignat * @throws InvalidKeyException if the private key is invalid. * @throws SignatureException if signing is not possible due to malformed data or private key. */ +@DeleteForDJVM @Throws(InvalidKeyException::class, SignatureException::class) fun KeyPair.sign(bytesToSign: ByteArray): DigitalSignature.WithKey = private.sign(bytesToSign, public) /** Helper function to sign the bytes of [bytesToSign] with a key pair. */ +@DeleteForDJVM @Throws(InvalidKeyException::class, SignatureException::class) fun KeyPair.sign(bytesToSign: OpaqueBytes): DigitalSignature.WithKey = sign(bytesToSign.bytes) @@ -64,6 +68,7 @@ fun KeyPair.sign(bytesToSign: OpaqueBytes): DigitalSignature.WithKey = sign(byte * @throws InvalidKeyException if the private key is invalid. * @throws SignatureException if signing is not possible due to malformed data or private key. */ +@DeleteForDJVM @Throws(InvalidKeyException::class, SignatureException::class) fun KeyPair.sign(signableData: SignableData): TransactionSignature = Crypto.doSign(this, signableData) diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 078cfcaa11..9814502433 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -457,11 +457,13 @@ $trustAnchor""", e, this, e.index) } } +@DeleteForDJVM inline fun T.signWithCert(signer: (SerializedBytes) -> DigitalSignatureWithCert): SignedDataWithCert { val serialised = serialize() return SignedDataWithCert(serialised, signer(serialised)) } +@DeleteForDJVM fun T.signWithCert(privateKey: PrivateKey, certificate: X509Certificate): SignedDataWithCert { return signWithCert { val signature = Crypto.doSign(privateKey, it.bytes) @@ -469,10 +471,12 @@ fun T.signWithCert(privateKey: PrivateKey, certificate: X509Certificat } } +@DeleteForDJVM inline fun SerializedBytes.sign(signer: (SerializedBytes) -> DigitalSignature.WithKey): SignedData { return SignedData(this, signer(this)) } +@DeleteForDJVM fun SerializedBytes.sign(keyPair: KeyPair): SignedData = SignedData(this, keyPair.sign(this.bytes)) fun ByteBuffer.copyBytes(): ByteArray = ByteArray(remaining()).also { get(it) } diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index f035192a48..601efcd8cf 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -91,6 +91,7 @@ data class SignedTransaction(val txBits: SerializedBytes, return descriptions } + @DeleteForDJVM @VisibleForTesting fun withAdditionalSignature(keyPair: KeyPair, signatureMetadata: SignatureMetadata): SignedTransaction { val signableData = SignableData(tx.id, signatureMetadata)