mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
ENT-3482: Move BC crypto service implementation to node api. (#5008)
* ENT-3482: Move BC crypto service implementation to node api. * ENT-3482: Added missing unit test.
This commit is contained in:
committed by
Matthew Nesbit
parent
374ae80ab1
commit
367c98ec7c
@ -0,0 +1,9 @@
|
||||
package net.corda.nodeapi.internal.cryptoservice
|
||||
|
||||
enum class SupportedCryptoServices {
|
||||
/** Identifier for [BCCryptoService]. */
|
||||
BC_SIMPLE
|
||||
// UTIMACO, // Utimaco HSM.
|
||||
// GEMALTO_LUNA, // Gemalto Luna HSM.
|
||||
// AZURE_KV // Azure key Vault.
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package net.corda.nodeapi.internal.cryptoservice.bouncycastle
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.crypto.sha256
|
||||
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 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.
|
||||
var certificateStore: CertificateStore = certificateStoreSupplier.get(true)
|
||||
|
||||
override fun generateKeyPair(alias: String, scheme: SignatureScheme): PublicKey {
|
||||
try {
|
||||
val keyPair = Crypto.generateKeyPair(scheme)
|
||||
importKey(alias, keyPair)
|
||||
return keyPair.public
|
||||
} catch (e: Exception) {
|
||||
throw CryptoServiceException("Cannot generate key for alias $alias and signature scheme ${scheme.schemeCodeName} (id ${scheme.schemeNumberID})", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun containsKey(alias: String): Boolean {
|
||||
return certificateStore.contains(alias)
|
||||
}
|
||||
|
||||
override fun getPublicKey(alias: String): PublicKey {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun defaultIdentitySignatureScheme(): SignatureScheme {
|
||||
return X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME
|
||||
}
|
||||
|
||||
override fun defaultTLSSignatureScheme(): SignatureScheme {
|
||||
return X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
|
||||
}
|
||||
|
||||
/**
|
||||
* If a node is running in [NodeConfiguration.devMode] and for backwards compatibility purposes, the same [KeyStore]
|
||||
* is reused outside [BCCryptoService] to update certificate paths. [resyncKeystore] will sync [BCCryptoService]'s
|
||||
* loaded [certificateStore] in memory with the contents of the corresponding [KeyStore] file.
|
||||
*/
|
||||
fun resyncKeystore() {
|
||||
certificateStore = certificateStoreSupplier.get(true)
|
||||
}
|
||||
|
||||
/** Import an already existing [KeyPair] to this [CryptoService]. */
|
||||
fun importKey(alias: String, keyPair: KeyPair) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package net.corda.nodeapi.internal.cryptoservice.bouncycastle
|
||||
|
||||
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 alias = "signature${signatureScheme.schemeNumberID}"
|
||||
val pubKey = cryptoService.generateKeyPair(alias, signatureScheme)
|
||||
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) }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user