[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:
Konstantinos Chalkias 2018-10-05 12:01:16 +01:00 committed by GitHub
parent d75fd7bd8a
commit fa4c54a080
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 4 deletions

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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())
}
}

View File

@ -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)

View File

@ -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) }

View File

@ -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)