mirror of
https://github.com/corda/corda.git
synced 2025-05-30 14:14:29 +00:00
[CORDA-2200][CORDA-2202] More tests for BCCryptoService and CryptoServiceException (#4190)
This commit is contained in:
parent
2caa082746
commit
81418ca7e7
@ -1,5 +1,6 @@
|
|||||||
package net.corda.nodeapi.internal.crypto
|
package net.corda.nodeapi.internal.crypto
|
||||||
|
|
||||||
|
import net.corda.core.crypto.Crypto.SPHINCS256_SHA256
|
||||||
import net.corda.core.crypto.SignatureScheme
|
import net.corda.core.crypto.SignatureScheme
|
||||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
@ -17,7 +18,9 @@ object ContentSignerBuilder {
|
|||||||
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner {
|
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner {
|
||||||
val sigAlgId = signatureScheme.signatureOID
|
val sigAlgId = signatureScheme.signatureOID
|
||||||
val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply {
|
val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply {
|
||||||
if (random != null) {
|
// TODO special handling for Sphincs due to a known BouncyCastle's Sphincs bug we reported.
|
||||||
|
// It is fixed in BC 161b12, so consider updating the below if-statement after updating BouncyCastle.
|
||||||
|
if (random != null && signatureScheme != SPHINCS256_SHA256) {
|
||||||
initSign(privateKey, random)
|
initSign(privateKey, random)
|
||||||
} else {
|
} else {
|
||||||
initSign(privateKey)
|
initSign(privateKey)
|
||||||
|
@ -38,3 +38,5 @@ interface CryptoService {
|
|||||||
*/
|
*/
|
||||||
fun getSigner(alias: String): ContentSigner
|
fun getSigner(alias: String): ContentSigner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open class CryptoServiceException(message: String?, cause: Throwable? = null) : Exception(message, cause)
|
||||||
|
@ -107,8 +107,8 @@ interface NodeConfiguration {
|
|||||||
|
|
||||||
fun makeCryptoService(): CryptoService {
|
fun makeCryptoService(): CryptoService {
|
||||||
return when(cryptoServiceName) {
|
return when(cryptoServiceName) {
|
||||||
SupportedCryptoServices.BC_SIMPLE -> BCCryptoService(this)
|
// Pick default BCCryptoService when null.
|
||||||
null -> BCCryptoService(this) // Pick default BCCryptoService when null.
|
SupportedCryptoServices.BC_SIMPLE, null -> BCCryptoService(this.myLegalName.x500Principal, this.signingCertificateStore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ data class FlowOverrideConfig(val overrides: List<FlowOverride> = listOf())
|
|||||||
data class FlowOverride(val initiator: String, val responder: String)
|
data class FlowOverride(val initiator: String, val responder: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Currently registered JMX Reporters
|
* Currently registered JMX Reporters.
|
||||||
*/
|
*/
|
||||||
enum class JmxReporterType {
|
enum class JmxReporterType {
|
||||||
JOLOKIA, NEW_RELIC
|
JOLOKIA, NEW_RELIC
|
||||||
|
@ -2,31 +2,39 @@ package net.corda.node.services.keys.cryptoservice
|
|||||||
|
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.newSecureRandom
|
import net.corda.core.crypto.newSecureRandom
|
||||||
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.nodeapi.internal.config.CertificateStore
|
import net.corda.nodeapi.internal.config.CertificateStore
|
||||||
|
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
|
||||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||||
|
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
|
* Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
|
||||||
* and a Java KeyStore in the form of [CertificateStore] to store private keys.
|
* and a Java KeyStore in the form of [CertificateStore] to store private keys.
|
||||||
* This service reuses the [NodeConfiguration.signingCertificateStore] to store keys.
|
* This service reuses the [NodeConfiguration.signingCertificateStore] to store keys.
|
||||||
*/
|
*/
|
||||||
class BCCryptoService(private val nodeConf: NodeConfiguration) : CryptoService {
|
class BCCryptoService(private val legalName: X500Principal, private val certificateStoreSupplier: CertificateStoreSupplier) : CryptoService {
|
||||||
|
|
||||||
// TODO check if keyStore exists.
|
// TODO check if keyStore exists.
|
||||||
// TODO make it private when E2ETestKeyManagementService does not require direct access to the private key.
|
// TODO make it private when E2ETestKeyManagementService does not require direct access to the private key.
|
||||||
internal var certificateStore: CertificateStore = nodeConf.signingCertificateStore.get(true)
|
internal var certificateStore: CertificateStore = certificateStoreSupplier.get(true)
|
||||||
|
|
||||||
override fun generateKeyPair(alias: String, schemeNumberID: Int): PublicKey {
|
override fun generateKeyPair(alias: String, schemeNumberID: Int): PublicKey {
|
||||||
|
try {
|
||||||
val keyPair = Crypto.generateKeyPair(Crypto.findSignatureScheme(schemeNumberID))
|
val keyPair = Crypto.generateKeyPair(Crypto.findSignatureScheme(schemeNumberID))
|
||||||
importKey(alias, keyPair)
|
importKey(alias, keyPair)
|
||||||
return keyPair.public
|
return keyPair.public
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CryptoServiceException("Cannot generate key for alias $alias and signature scheme with id $schemeNumberID", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun containsKey(alias: String): Boolean {
|
override fun containsKey(alias: String): Boolean {
|
||||||
@ -34,17 +42,29 @@ class BCCryptoService(private val nodeConf: NodeConfiguration) : CryptoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getPublicKey(alias: String): PublicKey {
|
override fun getPublicKey(alias: String): PublicKey {
|
||||||
|
try {
|
||||||
return certificateStore.query { getPublicKey(alias) }
|
return certificateStore.query { getPublicKey(alias) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CryptoServiceException("Cannot get public key for alias $alias", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sign(alias: String, data: ByteArray): ByteArray {
|
override fun sign(alias: String, data: ByteArray): ByteArray {
|
||||||
return Crypto.doSign(certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) } , data)
|
try {
|
||||||
|
return Crypto.doSign(certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }, data)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CryptoServiceException("Cannot sign using the key with alias $alias. SHA256 of data to be signed: ${data.sha256()}", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSigner(alias: String): ContentSigner {
|
override fun getSigner(alias: String): ContentSigner {
|
||||||
|
try {
|
||||||
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
|
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
|
||||||
val signatureScheme = Crypto.findSignatureScheme(privateKey)
|
val signatureScheme = Crypto.findSignatureScheme(privateKey)
|
||||||
return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
|
return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CryptoServiceException("Cannot get Signer for key with alias $alias", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,14 +73,18 @@ class BCCryptoService(private val nodeConf: NodeConfiguration) : CryptoService {
|
|||||||
* loaded [certificateStore] in memory with the contents of the corresponding [KeyStore] file.
|
* loaded [certificateStore] in memory with the contents of the corresponding [KeyStore] file.
|
||||||
*/
|
*/
|
||||||
fun resyncKeystore() {
|
fun resyncKeystore() {
|
||||||
certificateStore = nodeConf.signingCertificateStore.get(true)
|
certificateStore = certificateStoreSupplier.get(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Import an already existing [KeyPair] to this [CryptoService]. */
|
/** Import an already existing [KeyPair] to this [CryptoService]. */
|
||||||
fun importKey(alias: String, keyPair: KeyPair) {
|
fun importKey(alias: String, keyPair: KeyPair) {
|
||||||
|
try {
|
||||||
// Store a self-signed certificate, as Keystore requires to store certificates instead of public keys.
|
// Store a self-signed certificate, as Keystore requires to store certificates instead of public keys.
|
||||||
// We could probably add a null cert, but we store a self-signed cert that will be used to retrieve the public key.
|
// We could probably add a null cert, but we store a self-signed cert that will be used to retrieve the public key.
|
||||||
val cert = X509Utilities.createSelfSignedCACertificate(nodeConf.myLegalName.x500Principal, keyPair)
|
val cert = X509Utilities.createSelfSignedCACertificate(legalName, keyPair)
|
||||||
certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
|
certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CryptoServiceException("Cannot import key with alias $alias", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
package net.corda.node.services.keys.cryptoservice
|
||||||
|
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
|
import net.corda.core.crypto.SignatureScheme
|
||||||
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.core.utilities.days
|
||||||
|
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||||
|
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 org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import java.time.Duration
|
||||||
|
import javax.security.auth.x500.X500Principal
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class BCCryptoServiceTests {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val clearData = "data".toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val temporaryFolder = TemporaryFolder()
|
||||||
|
private lateinit var signingCertificateStore: CertificateStoreSupplier
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
val baseDirectory = temporaryFolder.root.toPath()
|
||||||
|
val certificatesDirectory = baseDirectory / "certificates"
|
||||||
|
signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BCCryptoService generate key pair and sign both data and cert`() {
|
||||||
|
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
|
||||||
|
// Testing every supported scheme.
|
||||||
|
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach { generateKeyAndSignForScheme(cryptoService, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateKeyAndSignForScheme(cryptoService: BCCryptoService, signatureScheme: SignatureScheme) {
|
||||||
|
val schemeNumberID = signatureScheme.schemeNumberID
|
||||||
|
val alias = "signature$schemeNumberID"
|
||||||
|
val pubKey = cryptoService.generateKeyPair(alias, schemeNumberID)
|
||||||
|
assertTrue { cryptoService.containsKey(alias) }
|
||||||
|
|
||||||
|
val signatureData = cryptoService.sign(alias, clearData)
|
||||||
|
assertTrue(Crypto.doVerify(pubKey, signatureData, clearData))
|
||||||
|
|
||||||
|
// Test that getSigner can indeed sign a certificate.
|
||||||
|
val signer = cryptoService.getSigner(alias)
|
||||||
|
val x500Principal = X500Principal("CN=Test")
|
||||||
|
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 365.days)
|
||||||
|
val certificate = X509Utilities.createCertificate(
|
||||||
|
CertificateType.CONFIDENTIAL_LEGAL_IDENTITY,
|
||||||
|
x500Principal,
|
||||||
|
pubKey,
|
||||||
|
signer,
|
||||||
|
x500Principal,
|
||||||
|
pubKey,
|
||||||
|
window)
|
||||||
|
|
||||||
|
certificate.checkValidity()
|
||||||
|
certificate.verify(pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `When key does not exist getPublicKey, sign and getSigner should throw`() {
|
||||||
|
val nonExistingAlias = "nonExistingAlias"
|
||||||
|
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
|
||||||
|
assertFalse { cryptoService.containsKey(nonExistingAlias) }
|
||||||
|
assertFailsWith<CryptoServiceException> { cryptoService.getPublicKey(nonExistingAlias) }
|
||||||
|
assertFailsWith<CryptoServiceException> { cryptoService.sign(nonExistingAlias, clearData) }
|
||||||
|
assertFailsWith<CryptoServiceException> { cryptoService.getSigner(nonExistingAlias) }
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user