mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
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:
committed by
Matthew Nesbit
parent
b37c422ff7
commit
46c073d212
@ -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.
|
||||
|
@ -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) }
|
||||
|
@ -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"
|
||||
|
Reference in New Issue
Block a user