[CORDA-2200][CORDA-2202] More tests for BCCryptoService and CryptoServiceException (#4190)

This commit is contained in:
Konstantinos Chalkias 2018-11-12 09:38:06 +00:00 committed by GitHub
parent 2caa082746
commit 81418ca7e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 19 deletions

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto.SPHINCS256_SHA256
import net.corda.core.crypto.SignatureScheme
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.operator.ContentSigner
@ -17,7 +18,9 @@ object ContentSignerBuilder {
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner {
val sigAlgId = signatureScheme.signatureOID
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)
} else {
initSign(privateKey)

View File

@ -38,3 +38,5 @@ interface CryptoService {
*/
fun getSigner(alias: String): ContentSigner
}
open class CryptoServiceException(message: String?, cause: Throwable? = null) : Exception(message, cause)

View File

@ -107,8 +107,8 @@ interface NodeConfiguration {
fun makeCryptoService(): CryptoService {
return when(cryptoServiceName) {
SupportedCryptoServices.BC_SIMPLE -> BCCryptoService(this)
null -> BCCryptoService(this) // Pick default BCCryptoService when null.
// 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)
/**
* Currently registered JMX Reporters
* Currently registered JMX Reporters.
*/
enum class JmxReporterType {
JOLOKIA, NEW_RELIC

View File

@ -2,31 +2,39 @@ package net.corda.node.services.keys.cryptoservice
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sha256
import net.corda.node.services.config.NodeConfiguration
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.X509Utilities
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.KeyStore
import java.security.PublicKey
import javax.security.auth.x500.X500Principal
/**
* Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
* and a Java KeyStore in the form of [CertificateStore] to store private 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 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 {
val keyPair = Crypto.generateKeyPair(Crypto.findSignatureScheme(schemeNumberID))
importKey(alias, keyPair)
return keyPair.public
try {
val keyPair = Crypto.generateKeyPair(Crypto.findSignatureScheme(schemeNumberID))
importKey(alias, keyPair)
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 {
@ -34,17 +42,29 @@ class BCCryptoService(private val nodeConf: NodeConfiguration) : CryptoService {
}
override fun getPublicKey(alias: String): PublicKey {
return certificateStore.query { getPublicKey(alias) }
try {
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 {
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 {
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
val signatureScheme = Crypto.findSignatureScheme(privateKey)
return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
try {
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
val signatureScheme = Crypto.findSignatureScheme(privateKey)
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.
*/
fun resyncKeystore() {
certificateStore = nodeConf.signingCertificateStore.get(true)
certificateStore = certificateStoreSupplier.get(true)
}
/** Import an already existing [KeyPair] to this [CryptoService]. */
fun importKey(alias: String, keyPair: KeyPair) {
// 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.
val cert = X509Utilities.createSelfSignedCACertificate(nodeConf.myLegalName.x500Principal, keyPair)
certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
try {
// 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.
val cert = X509Utilities.createSelfSignedCACertificate(legalName, keyPair)
certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
} catch (e: Exception) {
throw CryptoServiceException("Cannot import key with alias $alias", e)
}
}
}

View File

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