mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +00:00
[CORDA-2063] Ensure signatures and BC operations always use newSecureRandom (#4020)
* special handling for Sphincs due a BC implementation issue * delete all sign operations from DJVM and stub out BC's default RNG * copy Crypto signing functions to deterministic.crypto.CryptoSignUtils as they are required for testing transaction signatures.
This commit is contained in:
parent
d75fd7bd8a
commit
fa4c54a080
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -457,11 +457,13 @@ $trustAnchor""", e, this, e.index)
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteForDJVM
|
||||
inline fun <T : Any> T.signWithCert(signer: (SerializedBytes<T>) -> DigitalSignatureWithCert): SignedDataWithCert<T> {
|
||||
val serialised = serialize()
|
||||
return SignedDataWithCert(serialised, signer(serialised))
|
||||
}
|
||||
|
||||
@DeleteForDJVM
|
||||
fun <T : Any> T.signWithCert(privateKey: PrivateKey, certificate: X509Certificate): SignedDataWithCert<T> {
|
||||
return signWithCert {
|
||||
val signature = Crypto.doSign(privateKey, it.bytes)
|
||||
@ -469,10 +471,12 @@ fun <T : Any> T.signWithCert(privateKey: PrivateKey, certificate: X509Certificat
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteForDJVM
|
||||
inline fun <T : Any> SerializedBytes<T>.sign(signer: (SerializedBytes<T>) -> DigitalSignature.WithKey): SignedData<T> {
|
||||
return SignedData(this, signer(this))
|
||||
}
|
||||
|
||||
@DeleteForDJVM
|
||||
fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> = SignedData(this, keyPair.sign(this.bytes))
|
||||
|
||||
fun ByteBuffer.copyBytes(): ByteArray = ByteArray(remaining()).also { get(it) }
|
||||
|
@ -91,6 +91,7 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
return descriptions
|
||||
}
|
||||
|
||||
@DeleteForDJVM
|
||||
@VisibleForTesting
|
||||
fun withAdditionalSignature(keyPair: KeyPair, signatureMetadata: SignatureMetadata): SignedTransaction {
|
||||
val signableData = SignableData(tx.id, signatureMetadata)
|
||||
|
Loading…
x
Reference in New Issue
Block a user