CORDA-2882: Added ability to specify signature scheme when signing. (#5050)

* CORDA-2882: Added ability to specify signature scheme when signing.

* CORDA-2882: Sign operation with algo specified does not now use Crypto service.

* CORDA-2882: Added jvmoverloads for sign operation.

* CORDA-2882: Removed unused imports.
This commit is contained in:
Adel El-Beik
2019-04-26 10:01:43 +01:00
committed by Matthew Nesbit
parent b37c422ff7
commit 46c073d212
3 changed files with 111 additions and 5 deletions

View File

@ -30,10 +30,11 @@ interface CryptoService {
/**
* Sign a [ByteArray] using the private key identified by the input alias.
* Returns the signature bytes whose format depends on the underlying signature scheme and it should
* be Java BouncyCastle compatible (i.e., ASN.1 DER-encoded for ECDSA).
* Returns the signature bytes formatted according to the signature scheme.
* The signAlgorithm if specified determines the signature scheme used for signing, if
* not specified then the signature scheme is based on the private key scheme.
*/
fun sign(alias: String, data: ByteArray): ByteArray
fun sign(alias: String, data: ByteArray, signAlgorithm: String? = null): ByteArray
/**
* Returns [ContentSigner] for the key identified by the input alias.

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.cryptoservice.bouncycastle
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sha256
import net.corda.nodeapi.internal.config.CertificateStore
@ -14,6 +15,7 @@ import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.KeyStore
import java.security.PublicKey
import java.security.Signature
import javax.security.auth.x500.X500Principal
/**
@ -49,14 +51,26 @@ class BCCryptoService(private val legalName: X500Principal, private val certific
}
}
override fun sign(alias: String, data: ByteArray): ByteArray {
@JvmOverloads
override fun sign(alias: String, data: ByteArray, signAlgorithm: String?): ByteArray {
try {
return Crypto.doSign(certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }, data)
return when(signAlgorithm) {
null -> Crypto.doSign(certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }, data)
else -> signWithAlgorithm(alias, data, signAlgorithm)
}
} catch (e: Exception) {
throw CryptoServiceException("Cannot sign using the key with alias $alias. SHA256 of data to be signed: ${data.sha256()}", e)
}
}
private fun signWithAlgorithm(alias: String, data: ByteArray, signAlgorithm: String): ByteArray {
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
val signature = Signature.getInstance(signAlgorithm, cordaBouncyCastleProvider)
signature.initSign(privateKey, newSecureRandom())
signature.update(data)
return signature.sign()
}
override fun getSigner(alias: String): ContentSigner {
try {
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.cryptoservice.bouncycastle
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
import net.corda.core.internal.div
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
@ -10,10 +11,20 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.i2p.crypto.eddsa.EdDSAEngine
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.sec.SECObjectIdentifiers
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.FileOutputStream
import java.security.*
import java.time.Duration
import javax.security.auth.x500.X500Principal
import kotlin.test.assertFailsWith
@ -31,6 +42,10 @@ class BCCryptoServiceTests {
val temporaryFolder = TemporaryFolder()
private lateinit var signingCertificateStore: CertificateStoreSupplier
@Rule
@JvmField
val temporaryKeystoreFolder = TemporaryFolder()
@Before
fun setUp() {
val baseDirectory = temporaryFolder.root.toPath()
@ -70,6 +85,82 @@ class BCCryptoServiceTests {
certificate.verify(pubKey)
}
@Test
fun `BCCryptoService generate key pair and sign with existing schemes`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
// Testing every supported scheme.
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY
&& it.signatureName != "SHA512WITHSPHINCS256"}.forEach {
val alias = "signature${it.schemeNumberID}"
val pubKey = cryptoService.generateKeyPair(alias, it)
assertTrue { cryptoService.containsKey(alias) }
val signatureData = cryptoService.sign(alias, clearData, it.signatureName)
assertTrue(Crypto.doVerify(pubKey, signatureData, clearData))
}
}
@Test
fun `BCCryptoService 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")}
assertTrue{signAndVerify(signAlgo = "SHA1withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")}
assertTrue{signAndVerify(signAlgo = "SHA224withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")}
assertTrue{signAndVerify(signAlgo = "SHA256withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")}
assertTrue{signAndVerify(signAlgo = "SHA384withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")}
assertTrue{signAndVerify(signAlgo = "SHA512withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")}
assertTrue{signAndVerify(signAlgo = "NONEwithECDSA", alias = "myKeyAlias", keyTypeAlgo = "EC")}
assertTrue{signAndVerify(signAlgo = "SHA1withECDSA", alias = "myKeyAlias", keyTypeAlgo = "EC")}
assertTrue{signAndVerify(signAlgo = "SHA224withECDSA", alias = "myKeyAlias", keyTypeAlgo = "EC")}
assertTrue{signAndVerify(signAlgo = "SHA256withECDSA", alias = "myKeyAlias", keyTypeAlgo = "EC")}
assertTrue{signAndVerify(signAlgo = "SHA384withECDSA", alias = "myKeyAlias", keyTypeAlgo = "EC")}
assertTrue{signAndVerify(signAlgo = "SHA512withECDSA", alias = "myKeyAlias", keyTypeAlgo = "EC")}
}
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))
assertTrue { cryptoService.containsKey(alias) }
val signatureData = cryptoService.sign(alias, clearData, signAlgo)
return verify(signAlgo, cryptoService.getPublicKey(alias), signatureData, clearData)
}
private fun verify(signAlgo: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean {
val sig = Signature.getInstance(signAlgo, BouncyCastleProvider())
sig.initVerify(publicKey)
sig.update(clearData)
return sig.verify(signatureData)
}
private fun createKeystore(alias: String, keyPair: KeyPair) : CertificateStoreSupplier {
val myPassword = "password"
val keyStoreFilename = "keys-with-more-algos.jks"
val keyStore = KeyStore.getInstance("pkcs12")
keyStore.load(null, null)
val baseDirectory = temporaryKeystoreFolder.root.toPath()
val certificatesDirectory = baseDirectory / keyStoreFilename
val x500Principal = X500Principal("CN=Test")
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 365.days)
val certificate = X509Utilities.createCertificate(
CertificateType.TLS,
x500Principal,
keyPair,
x500Principal,
keyPair.public,
window)
keyStore.setKeyEntry(alias, keyPair.private, "password".toCharArray(), arrayOf(certificate))
FileOutputStream(certificatesDirectory.toString()).use { keyStore.store(it, myPassword.toCharArray())}
return CertificateStoreStubs.Signing.withCertificatesDirectory(
certificatesDirectory = baseDirectory,
password = myPassword,
keyPassword = myPassword,
certificateStoreFileName = keyStoreFilename)
}
@Test
fun `When key does not exist getPublicKey, sign and getSigner should throw`() {
val nonExistingAlias = "nonExistingAlias"