[CORDA-2011] [CORDA-2057] CryptoService interface and BC HSM simulation (#4099)

This commit is contained in:
Konstantinos Chalkias
2018-11-06 12:57:13 +00:00
committed by GitHub
parent 1f5436dcfc
commit 106eb9df4a
29 changed files with 775 additions and 220 deletions

View File

@ -92,7 +92,7 @@ class NodeRegistrationTest {
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"),
notaryCustomOverrides = mapOf("devMode" to false)
) {
val (alice, genevieve) = listOf(
val (alice, genevieve) = listOf(
startNode(providedName = aliceName, customOverrides = mapOf("devMode" to false)),
startNode(providedName = genevieveName, customOverrides = mapOf("devMode" to false))
).transpose().getOrThrow()

View File

@ -9,21 +9,19 @@ import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sign
import net.corda.core.flows.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.*
import net.corda.core.node.*
import net.corda.core.node.services.*
@ -56,8 +54,9 @@ import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.keys.KeyManagementServiceInternal
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.node.services.messaging.DeduplicationHandler
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.NetworkMapClient
@ -75,14 +74,15 @@ import net.corda.node.utilities.*
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX
import net.corda.nodeapi.internal.persistence.*
import net.corda.nodeapi.internal.storeLegalIdentity
import net.corda.tools.shell.InteractiveShell
import org.apache.activemq.artemis.utils.ReusableLatch
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
@ -90,7 +90,6 @@ import org.slf4j.Logger
import rx.Observable
import rx.Scheduler
import java.io.IOException
import java.lang.UnsupportedOperationException
import java.lang.reflect.InvocationTargetException
import java.nio.file.Paths
import java.security.KeyPair
@ -167,6 +166,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize()
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
val cryptoService = configuration.makeCryptoService()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
@ -262,6 +262,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private fun initKeyStores(): X509Certificate {
if (configuration.devMode) {
configuration.configureWithDevSSLCertificate()
// configureWithDevSSLCertificate is a devMode process that writes directly to keystore files, so
// we should re-synchronise BCCryptoService with the updated keystore file.
if (cryptoService is BCCryptoService) {
cryptoService.resyncKeystore()
}
}
return validateKeyStores()
}
@ -300,7 +305,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
log.info("Node starting up ...")
val trustRoot = initKeyStores()
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
initialiseJVMAgents()
schemaService.mappedSchemasWarnings().forEach {
@ -326,6 +330,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
startDatabase()
val (identity, identityKeyPair) = obtainIdentity()
X509Utilities.validateCertPath(trustRoot, identity.certPath)
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction {
@ -344,6 +351,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
attachments.start()
cordappProvider.start(netParams.whitelistedContractImplementations)
nodeProperties.start()
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet.
keyManagementService.start(keyPairs)
val notaryService = makeNotaryService(myNotaryIdentity)
installCordaServices(myNotaryIdentity)
@ -421,7 +431,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val nodeInfoFromDb = getPreviousNodeInfoIfPresent(identity)
val nodeInfo = if (potentialNodeInfo == nodeInfoFromDb?.copy(serial = 0)) {
// The node info hasn't changed. We use the one from the database to preserve the serial.
log.debug("Node-info hasn't changed")
@ -436,7 +445,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val nodeInfoAndSigned = NodeInfoAndSigned(nodeInfo) { publicKey, serialised ->
val privateKey = keyPairs.single { it.public == publicKey }.private
privateKey.sign(serialised.bytes)
DigitalSignature(cryptoService.sign((privateKey as AliasPrivateKey).alias, serialised.bytes))
}
// Write the node-info file even if nothing's changed, just in case the file has been deleted.
@ -651,9 +660,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return try {
// The following will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeyStore = configuration.p2pSslOptions.keyStore.get()
val identitiesKeyStore = configuration.signingCertificateStore.get()
val signingCertificateStore = configuration.signingCertificateStore.get()
val trustStore = configuration.p2pSslOptions.trustStore.get()
AllCertificateStores(trustStore, sslKeyStore, identitiesKeyStore)
AllCertificateStores(trustStore, sslKeyStore, signingCertificateStore)
} catch (e: IOException) {
log.error("IO exception while trying to validate keystores and truststore", e)
null
@ -762,7 +771,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet.
return PersistentKeyManagementService(cacheFactory, identityService, database)
return BasicHSMKeyManagementService(cacheFactory,identityService, database, cryptoService)
}
open fun stop() {
@ -787,50 +796,47 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
nodeInfo: NodeInfo,
myNotaryIdentity: PartyAndCertificate?,
networkParameters: NetworkParameters)
/** Loads or generates the node's legal identity and key-pair. */
/**
* Loads or generates the node's legal identity and key-pair.
* Note that obtainIdentity returns a KeyPair with an [AliasPrivateKey].
*/
private fun obtainIdentity(): Pair<PartyAndCertificate, KeyPair> {
val keyStore = configuration.signingCertificateStore.get()
val legalName = configuration.myLegalName
val legalIdentityPrivateKeyAlias = "$NODE_IDENTITY_ALIAS_PREFIX-private-key"
// TODO: Integrate with Key management service?
val privateKeyAlias = "$NODE_IDENTITY_ALIAS_PREFIX-private-key"
if (privateKeyAlias !in keyStore) {
log.info("$privateKeyAlias not found in key store, generating fresh key!")
keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair())
if (!cryptoService.containsKey(legalIdentityPrivateKeyAlias)) {
log.info("$legalIdentityPrivateKeyAlias not found in key store, generating fresh key!")
storeLegalIdentity(legalIdentityPrivateKeyAlias)
}
val (x509Cert, keyPair) = keyStore.query { getCertificateAndKeyPair(privateKeyAlias, keyStore.entryPassword) }
val signingCertificateStore = configuration.signingCertificateStore.get()
val x509Cert = signingCertificateStore.query { getCertificate(legalIdentityPrivateKeyAlias) }
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val certificates = keyStore.query { getCertificateChain(privateKeyAlias) }
val certificates: List<X509Certificate> = signingCertificateStore.query { getCertificateChain(legalIdentityPrivateKeyAlias) }
check(certificates.first() == x509Cert) {
"Certificates from key store do not line up!"
}
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
val legalName = configuration.myLegalName
if (subject != legalName) {
throw ConfigurationException("The name '$legalName' for $NODE_IDENTITY_ALIAS_PREFIX doesn't match what's in the key store: $subject")
}
val certPath = X509Utilities.buildCertPath(certificates)
return Pair(PartyAndCertificate(certPath), keyPair)
return getPartyAndCertificatePlusAliasKeyPair(certificates, legalIdentityPrivateKeyAlias)
}
/** Loads pre-generated notary service cluster identity. */
private fun loadNotaryClusterIdentity(serviceLegalName: CordaX500Name): Pair<PartyAndCertificate, KeyPair> {
val keyStore = configuration.signingCertificateStore.get()
val privateKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key"
val keyPair = keyStore.query { getCertificateAndKeyPair(privateKeyAlias, keyStore.entryPassword) }.keyPair
val compositeKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key"
val certificates = if (compositeKeyAlias in keyStore) {
val certificate = keyStore[compositeKeyAlias]
val signingCertificateStore = configuration.signingCertificateStore.get()
val certificates = if (cryptoService.containsKey(compositeKeyAlias)) {
val certificate = signingCertificateStore[compositeKeyAlias]
// We have to create the certificate chain for the composite key manually, this is because we don't have a keystore
// provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate +
// the tail of the private key certificates, as they are both signed by the same certificate chain.
listOf(certificate) + keyStore.query { getCertificateChain(privateKeyAlias) }.drop(1)
listOf(certificate) + signingCertificateStore.query { getCertificateChain(privateKeyAlias) }.drop(1)
} else throw IllegalStateException("The identity public key for the notary service $serviceLegalName was not found in the key store.")
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
@ -838,11 +844,43 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
throw ConfigurationException("The name of the notary service '$serviceLegalName' for $DISTRIBUTED_NOTARY_ALIAS_PREFIX doesn't " +
"match what's in the key store: $subject. You might need to adjust the configuration of `notary.serviceLegalName`.")
}
return getPartyAndCertificatePlusAliasKeyPair(certificates, privateKeyAlias)
}
// Method to create a Pair<PartyAndCertificate, KeyPair>, where KeyPair uses an AliasPrivateKey.
private fun getPartyAndCertificatePlusAliasKeyPair(certificates: List<X509Certificate>, privateKeyAlias: String): Pair<PartyAndCertificate, KeyPair> {
val certPath = X509Utilities.buildCertPath(certificates)
val keyPair = KeyPair(cryptoService.getPublicKey(privateKeyAlias), AliasPrivateKey(privateKeyAlias))
return Pair(PartyAndCertificate(certPath), keyPair)
}
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
private fun storeLegalIdentity(alias: String): PartyAndCertificate {
val legalIdentityPublicKey = generateKeyPair(alias)
val signingCertificateStore = configuration.signingCertificateStore.get()
val nodeCaCertPath = signingCertificateStore.value.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
val nodeCaCert = nodeCaCertPath[0] // This should be the same with signingCertificateStore[alias]
val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY,
nodeCaCert.subjectX500Principal,
nodeCaCert.publicKey,
cryptoService.getSigner(X509Utilities.CORDA_CLIENT_CA),
nodeCaCert.subjectX500Principal,
legalIdentityPublicKey,
// TODO this might be smaller than DEFAULT_VALIDITY_WINDOW, shall we strictly apply DEFAULT_VALIDITY_WINDOW?
X509Utilities.getCertificateValidityWindow(
DEFAULT_VALIDITY_WINDOW.first,
DEFAULT_VALIDITY_WINDOW.second,
nodeCaCert)
)
val identityCertPath = listOf(identityCert) + nodeCaCertPath
signingCertificateStore.setCertPathOnly(alias, identityCertPath)
return PartyAndCertificate(X509Utilities.buildCertPath(identityCertPath))
}
protected open fun generateKeyPair(alias: String) = cryptoService.generateKeyPair(alias, X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME.schemeNumberID)
protected open fun makeVaultService(keyManagementService: KeyManagementService,
services: ServicesForResolution,

View File

@ -71,7 +71,7 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
HTTPNetworkRegistrationService(
requireNotNull(conf.networkServices),
versionInfo),
nodeRegistration).buildKeystore()
nodeRegistration).generateKeysAndRegister()
// Minimal changes to make registration tool create node identity.
// TODO: Move node identity generation logic from node to registration helper.
@ -107,4 +107,3 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
initialRegistration(node.configuration)
}
}

View File

@ -67,10 +67,11 @@ object ConfigHelper {
* Strictly for dev only automatically construct a server certificate/private key signed from
* the CA certs in Node resources. Then provision KeyStores into certificates folder under node path.
*/
// TODO Move this to KeyStoreConfigHelpers
// TODO Move this to KeyStoreConfigHelpers.
// TODO consider taking CryptoService as an input.
fun NodeConfiguration.configureWithDevSSLCertificate() = p2pSslOptions.configureDevKeyAndTrustStores(myLegalName, signingCertificateStore, certificatesDirectory)
// TODO Move this to KeyStoreConfigHelpers
// TODO Move this to KeyStoreConfigHelpers.
fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path) {
val specifiedTrustStore = trustStore.getOptional()
@ -85,7 +86,7 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
loadDevCaTrustStore().copyTo(trustStore.get(true))
}
if (keyStore.getOptional() == null || signingCertificateStore.getOptional() == null) {
if (specifiedKeyStore == null || specifiedSigningStore == null) {
val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.storePassword, signingCertificateStore.entryPassword).get(true).also { it.registerDevSigningCertificates(myLegalName) }
FileBasedCertificateStoreSupplier(keyStore.path, keyStore.storePassword, keyStore.entryPassword).get(true).also { it.registerDevP2pCertificates(myLegalName) }

View File

@ -10,10 +10,13 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.*
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.nodeapi.internal.config.*
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.tools.shell.SSHDConfiguration
import org.slf4j.Logger
import java.net.URL
@ -73,6 +76,8 @@ interface NodeConfiguration {
val baseDirectory: Path
val certificatesDirectory: Path
// signingCertificateStore is used to store certificate chains.
// However, BCCryptoService is reusing this to store keys as well.
val signingCertificateStore: FileBasedCertificateStoreSupplier
val p2pSslOptions: MutualSslConfiguration
@ -81,6 +86,11 @@ interface NodeConfiguration {
val cordappSignerKeyFingerprintBlacklist: List<String>
// TODO At the moment this is just an identifier for the desired CryptoService engine. Consider using a classname to
// to allow for pluggable implementations.
val cryptoServiceName: SupportedCryptoServices?
val cryptoServiceConf: String? // Location for the cryptoService conf file.
fun validate(): List<String>
companion object {
@ -99,6 +109,13 @@ interface NodeConfiguration {
val defaultJmxReporterType = JmxReporterType.JOLOKIA
}
fun makeCryptoService(): CryptoService {
return when(cryptoServiceName) {
SupportedCryptoServices.BC_SIMPLE -> BCCryptoService(this)
null -> BCCryptoService(this) // Pick default BCCryptoService when null.
}
}
}
data class FlowOverrideConfig(val overrides: List<FlowOverride> = listOf())
@ -219,11 +236,13 @@ data class NodeConfigurationImpl(
override val cordappDirectories: List<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT),
override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA,
override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() }
override val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() },
override val cryptoServiceName: SupportedCryptoServices? = null,
override val cryptoServiceConf: String? = null
) : NodeConfiguration {
companion object {
private val logger = loggerFor<NodeConfigurationImpl>()
// private val supportedCryptoServiceNames = setOf("BC", "UTIMACO", "GEMALTO-LUNA", "AZURE-KEY-VAULT")
}
private val actualRpcSettings: NodeRpcSettings
@ -276,6 +295,14 @@ data class NodeConfigurationImpl(
return errors
}
private fun validateCryptoService(): List<String> {
val errors = mutableListOf<String>()
if (cryptoServiceName == null && cryptoServiceConf != null) {
errors += "cryptoServiceName is null, but cryptoServiceConf is set to $cryptoServiceConf"
}
return errors
}
override fun validate(): List<String> {
val errors = mutableListOf<String>()
errors += validateDevModeOptions()
@ -288,6 +315,7 @@ data class NodeConfigurationImpl(
errors += validateTlsCertCrlConfig()
errors += validateNetworkServices()
errors += validateH2Settings()
errors += validateCryptoService()
return errors
}

View File

@ -0,0 +1,155 @@
package net.corda.node.services.keys
import net.corda.core.crypto.*
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Lob
/**
* A persistent re-implementation of [E2ETestKeyManagementService] to support CryptoService for initial keys and
* database storage for anonymous fresh keys.
*
* This is not the long-term implementation. See the list of items in the above class.
*
* This class needs database transactions to be in-flight during method calls and init.
*/
class BasicHSMKeyManagementService(cacheFactory: NamedCacheFactory, val identityService: PersistentIdentityService,
private val database: CordaPersistence, private val cryptoService: CryptoService) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
@Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
class PersistentKey(
@Id
@Column(name = "public_key_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String,
@Lob
@Column(name = "public_key", nullable = false)
var publicKey: ByteArray = EMPTY_BYTE_ARRAY,
@Lob
@Column(name = "private_key", nullable = false)
var privateKey: ByteArray = EMPTY_BYTE_ARRAY
) {
constructor(publicKey: PublicKey, privateKey: PrivateKey)
: this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded)
}
private companion object {
fun createKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<PublicKey, PrivateKey, PersistentKey, String> {
return AppendOnlyPersistentMap(
cacheFactory = cacheFactory,
name = "BasicHSMKeyManagementService_keys",
toPersistentEntityKey = { it.toStringShort() },
fromPersistentEntity = { Pair(Crypto.decodePublicKey(it.publicKey), Crypto.decodePrivateKey(
it.privateKey)) },
toPersistentEntity = { key: PublicKey, value: PrivateKey ->
PersistentKey(key, value)
},
persistentEntityClass = PersistentKey::class.java
)
}
}
// Maintain a map from PublicKey to alias for the initial keys.
private val originalKeysMap = mutableMapOf<PublicKey, String>()
// A map for anonymous keys.
private val keysMap = createKeyMap(cacheFactory)
override fun start(initialKeyPairs: Set<KeyPair>) {
initialKeyPairs.forEach {
require(it.private is AliasPrivateKey) { "${this.javaClass.name} supports AliasPrivateKeys only, but ${it.private.algorithm} key was found" }
originalKeysMap[Crypto.toSupportedPublicKey(it.public)] = (it.private as AliasPrivateKey).alias
}
}
override val keys: Set<PublicKey> get() = database.transaction { originalKeysMap.keys.plus(keysMap.allPersisted().map { it.first }.toSet()) }
private fun containsPublicKey(publicKey: PublicKey): Boolean {
return (publicKey in originalKeysMap || publicKey in keysMap)
}
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = database.transaction {
identityService.stripCachedPeerKeys(candidateKeys).filter { containsPublicKey(it) } // TODO: bulk cache access.
}
// Unlike initial keys, freshkey() is related confidential keys and it utilises platform's software key generation
// thus, without using [cryptoService]).
override fun freshKey(): PublicKey {
val keyPair = generateKeyPair()
database.transaction {
keysMap[keyPair.public] = keyPair.private
}
return keyPair.public
}
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey))
}
private fun getSigner(publicKey: PublicKey): ContentSigner {
val signingPublicKey = getSigningPublicKey(publicKey)
return if (signingPublicKey in originalKeysMap) {
cryptoService.getSigner(originalKeysMap[signingPublicKey]!!)
} else {
getSigner(getSigningKeyPair(signingPublicKey))
}
}
// Get [KeyPair] for the input [publicKey]. This is used for fresh keys, in which we have access to the private key material.
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
return database.transaction {
KeyPair(publicKey, keysMap[publicKey]!!)
}
}
// It looks for the PublicKey in the (potentially) CompositeKey that is ours.
// TODO what if we own two or more leaves of a CompositeKey?
private fun getSigningPublicKey(publicKey: PublicKey): PublicKey {
return publicKey.keys.first { containsPublicKey(it) }
}
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
val signingPublicKey = getSigningPublicKey(publicKey)
return if (signingPublicKey in originalKeysMap) {
DigitalSignature.WithKey(signingPublicKey, cryptoService.sign(originalKeysMap[signingPublicKey]!!, bytes))
} else {
val keyPair = getSigningKeyPair(signingPublicKey)
keyPair.sign(bytes)
}
}
// TODO: A full KeyManagementService implementation needs to record activity to the Audit Service and to limit
// signing to appropriately authorised contexts and initiating users.
override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
val signingPublicKey = getSigningPublicKey(publicKey)
return if (signingPublicKey in originalKeysMap) {
val sigKey: SignatureScheme = Crypto.findSignatureScheme(signingPublicKey)
val sigMetaData: SignatureScheme = Crypto.findSignatureScheme(signableData.signatureMetadata.schemeNumberID)
require(sigKey == sigMetaData || sigMetaData == Crypto.COMPOSITE_KEY) {
"Metadata schemeCodeName: ${sigMetaData.schemeCodeName} is not aligned with the key type: ${sigKey.schemeCodeName}."
}
val signatureBytes = cryptoService.sign(originalKeysMap[signingPublicKey]!!, signableData.serialize().bytes)
TransactionSignature(signatureBytes, signingPublicKey, signableData.signatureMetadata)
} else {
val keyPair = getSigningKeyPair(signingPublicKey)
keyPair.sign(signableData)
}
}
}

View File

@ -5,6 +5,9 @@ import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.PrivateKey
@ -24,7 +27,7 @@ import javax.annotation.concurrent.ThreadSafe
* etc.
*/
@ThreadSafe
class E2ETestKeyManagementService(val identityService: IdentityService) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
class E2ETestKeyManagementService(val identityService: IdentityService, private val cryptoService: CryptoService? = null) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
private class InnerState {
val keys = HashMap<PublicKey, PrivateKey>()
}
@ -32,12 +35,20 @@ class E2ETestKeyManagementService(val identityService: IdentityService) : Single
private val mutex = ThreadBox(InnerState())
// Accessing this map clones it.
override val keys: Set<PublicKey> get() = mutex.locked { keys.keys }
// Maintain a map from PublicKey to alias for the initial keys.
val keyPairs: Set<KeyPair> get() = mutex.locked { keys.map { KeyPair(it.key, it.value) }.toSet() }
override fun start(initialKeyPairs: Set<KeyPair>) {
mutex.locked {
for (key in initialKeyPairs) {
keys[key.public] = key.private
var privateKey = key.private
if (privateKey is AliasPrivateKey && cryptoService is BCCryptoService) {
privateKey = cryptoService.certificateStore.query {
getPrivateKey((privateKey as AliasPrivateKey).alias, cryptoService.certificateStore.entryPassword)
}
}
keys[key.public] = privateKey
}
}
}

View File

@ -0,0 +1,66 @@
package net.corda.node.services.keys.cryptoservice
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.newSecureRandom
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.KeyStore
import java.security.PublicKey
/**
* 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 {
// 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)
override fun generateKeyPair(alias: String, schemeNumberID: Int): PublicKey {
val keyPair = Crypto.generateKeyPair(Crypto.findSignatureScheme(schemeNumberID))
importKey(alias, keyPair)
return keyPair.public
}
override fun containsKey(alias: String): Boolean {
return certificateStore.contains(alias)
}
override fun getPublicKey(alias: String): PublicKey {
return certificateStore.query { getPublicKey(alias) }
}
override fun sign(alias: String, data: ByteArray): ByteArray {
return Crypto.doSign(certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) } , data)
}
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())
}
/**
* 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 = nodeConf.signingCertificateStore.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) }
}
}

View File

@ -0,0 +1,9 @@
package net.corda.node.services.keys.cryptoservice
enum class SupportedCryptoServices {
/** Identifier for [BCCryptoService]. */
BC_SIMPLE
// UTIMACO, // Utimaco HSM.
// GEMALTO_LUNA, // Gemalto Luna HSM.
// AZURE_KV // Azure key Vault.
}

View File

@ -12,12 +12,12 @@ import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.SchemaService.SchemaOptions
import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.P2PMessageDeduplicator
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.VaultSchemaV1
@ -35,6 +35,7 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
object NodeCoreV1 : MappedSchema(schemaFamily = NodeCore.javaClass, version = 1,
mappedTypes = listOf(DBCheckpointStorage.DBCheckpoint::class.java,
DBTransactionStorage.DBTransaction::class.java,
BasicHSMKeyManagementService.PersistentKey::class.java,
PersistentKeyManagementService.PersistentKey::class.java,
NodeSchedulerService.PersistentScheduledState::class.java,
NodeAttachmentService.DBAttachment::class.java,

View File

@ -56,6 +56,7 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
name == "DeduplicationChecker_watermark" -> caffeine
name == "BFTNonValidatingNotaryService_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "RaftUniquenessProvider_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)
else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?")
}
}

View File

@ -1,28 +1,31 @@
package net.corda.node.utilities.registration
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.utilities.contextLogger
import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.util.io.pem.PemObject
import java.io.IOException
import java.io.StringWriter
import java.net.ConnectException
import java.nio.file.Path
import java.security.KeyPair
import java.security.KeyStore
import java.security.PublicKey
import java.security.cert.X509Certificate
import java.time.Duration
@ -33,23 +36,25 @@ import javax.security.auth.x500.X500Principal
* Helper for managing the node registration process, which checks for any existing certificates and requests them if
* needed.
*/
// TODO: Use content signer instead of keypairs.
open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
private val signingCertificateStore: CertificateStoreSupplier,
private val myLegalName: CordaX500Name,
private val emailAddress: String,
private val certService: NetworkRegistrationService,
private val networkRootTrustStorePath: Path,
networkRootTrustStorePassword: String,
private val keyAlias: String,
private val certRole: CertRole,
private val nextIdleDuration: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) {
open class NetworkRegistrationHelper(
config: NodeConfiguration,
private val certService: NetworkRegistrationService,
private val networkRootTrustStorePath: Path,
networkRootTrustStorePassword: String,
private val nodeCaKeyAlias: String,
private val certRole: CertRole,
private val nextIdleDuration: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))
) {
companion object {
const val SELF_SIGNED_PRIVATE_KEY = "SelfSignedPrivateKey"
val logger = contextLogger()
}
private val certificatesDirectory: Path = config.certificatesDirectory
private val myLegalName: CordaX500Name = config.myLegalName
private val emailAddress: String = config.emailAddress
private val cryptoService = config.makeCryptoService()
private val certificateStore = config.signingCertificateStore.get(true)
private val requestIdStore = certificatesDirectory / "certificate-request-id.txt"
protected val rootTrustStore: X509KeyStore
protected val rootCert: X509Certificate
@ -64,57 +69,67 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
}
/**
* Ensure the initial keystore for a node is set up.
* Ensure the initial keys and certificates for a node are set up.
*
* This checks the "config.certificatesDirectory" field for certificates required to connect to a Corda network.
* If the certificates are not found, a PKCS #10 certification request will be submitted to the
* Corda network permissioning server via [NetworkRegistrationService]. This process will enter a polling loop until
* the request has been approved, and then the certificate chain will be downloaded and stored in [KeyStore] reside in
* the certificates directory.
* the request has been approved, and then the certificate chain will be downloaded and stored in [certificateStore].
*
* @throws CertificateRequestException if the certificate retrieved by doorman is invalid.
*/
fun buildKeystore() {
fun generateKeysAndRegister() {
certificatesDirectory.createDirectories()
val nodeKeyStore = signingCertificateStore.get(createNew = true)
if (keyAlias in nodeKeyStore) {
// We need this in case cryptoService and certificateStore share the same KeyStore (for backwards compatibility purposes).
// If we didn't, then an update to cryptoService wouldn't be reflected to certificateStore that is already loaded in memory.
val certStore: CertificateStore = if (cryptoService is BCCryptoService) cryptoService.certificateStore else certificateStore
// SELF_SIGNED_PRIVATE_KEY is used as progress indicator.
if (certStore.contains(nodeCaKeyAlias) && !certStore.contains(SELF_SIGNED_PRIVATE_KEY)) {
println("Certificate already exists, Corda node will now terminate...")
return
}
// TODO: Use different password for private key.
val privateKeyPassword = nodeKeyStore.password
val tlsCrlIssuerCert = getTlsCrlIssuerCert()
// We use SELF_SIGNED_PRIVATE_KEY as progress indicator so we just store a dummy key and cert.
// When registration succeeds, this entry should be deleted.
certStore.query { setPrivateKey(SELF_SIGNED_PRIVATE_KEY, AliasPrivateKey(SELF_SIGNED_PRIVATE_KEY), listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), certificateStore.entryPassword) }
val nodeCaPublicKey = loadOrGenerateKeyPair()
val requestId = submitOrResumeCertificateSigningRequest(nodeCaPublicKey, cryptoService.getSigner(nodeCaKeyAlias))
val nodeCaCertificates = pollServerForCertificates(requestId)
validateCertificates(nodeCaPublicKey, nodeCaCertificates)
certStore.setCertPathOnly(nodeCaKeyAlias, nodeCaCertificates)
certStore.value.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
certStore.value.save()
println("Private key '$nodeCaKeyAlias' and its certificate-chain stored successfully.")
onSuccess(nodeCaPublicKey, cryptoService.getSigner(nodeCaKeyAlias), nodeCaCertificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name())
// All done, clean up temp files.
requestIdStore.deleteIfExists()
}
private fun loadOrGenerateKeyPair(): PublicKey {
return if (cryptoService.containsKey(nodeCaKeyAlias)) {
cryptoService.getPublicKey(nodeCaKeyAlias)!!
} else {
cryptoService.generateKeyPair(nodeCaKeyAlias, X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME.schemeNumberID)
}
}
private fun getTlsCrlIssuerCert(): X509Certificate? {
val tlsCrlIssuerCert = validateAndGetTlsCrlIssuerCert()
if (tlsCrlIssuerCert == null && isTlsCrlIssuerCertRequired()) {
System.err.println("""tlsCrlIssuerCert config does not match the root certificate issuer and nor is there any other certificate in the trust store with a matching issuer.
| Please make sure the config is correct or that the correct certificate for the CRL issuer is added to the node's trust store.
| The node will now terminate.""".trimMargin())
| Please make sure the config is correct or that the correct certificate for the CRL issuer is added to the node's trust store.
| The node will now terminate.""".trimMargin())
throw IllegalArgumentException("TLS CRL issuer certificate not found in the trust store.")
}
val keyPair = nodeKeyStore.loadOrCreateKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
val requestId = try {
submitOrResumeCertificateSigningRequest(keyPair)
} catch (e: Exception) {
throw if (e is ConnectException || e is ServiceUnavailableException || e is IOException) {
NodeRegistrationException(e.message, e)
} else e
}
val certificates = try {
pollServerForCertificates(requestId)
} catch (certificateRequestException: CertificateRequestException) {
System.err.println(certificateRequestException.message)
System.err.println("Please make sure the details in configuration file are correct and try again.")
System.err.println("Corda node will now terminate.")
requestIdStore.deleteIfExists()
throw certificateRequestException
}
validateCertificates(keyPair.public, certificates)
storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias, privateKeyPassword)
onSuccess(keyPair, certificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name())
// All done, clean up temp files.
requestIdStore.deleteIfExists()
return tlsCrlIssuerCert
}
private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List<X509Certificate>) {
@ -149,18 +164,6 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
println("Certificate signing request approved, storing private key with the certificate chain.")
}
private fun storePrivateKeyWithCertificates(nodeKeystore: CertificateStore, keyPair: KeyPair, certificates: List<X509Certificate>, keyAlias: String, keyPassword: String) {
// Save private key and certificate chain to the key store.
with(nodeKeystore.value) {
setPrivateKey(keyAlias, keyPair.private, certificates, keyPassword = keyPassword)
// The key was temporarily stored as SELF_SIGNED_PRIVATE_KEY, but now that it's signed by the Doorman we
// can delete this old record.
internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
save()
}
println("Private key '$keyAlias' and certificate stored in node signing keystore.")
}
private fun CertificateStore.loadOrCreateKeyPair(alias: String, entryPassword: String = password): KeyPair {
// Create or load self signed keypair from the key store.
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
@ -184,65 +187,80 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
* @return List of certificate chain.
*/
private fun pollServerForCertificates(requestId: String): List<X509Certificate> {
println("Start polling server for certificate signing approval.")
// Poll server to download the signed certificate once request has been approved.
var idlePeriodDuration: Duration? = null
while (true) {
try {
val (pollInterval, certificates) = certService.retrieveCertificates(requestId)
if (certificates != null) {
return certificates
}
Thread.sleep(pollInterval.toMillis())
} catch (e: ServiceUnavailableException) {
idlePeriodDuration = nextIdleDuration(idlePeriodDuration)
if (idlePeriodDuration != null) {
Thread.sleep(idlePeriodDuration.toMillis())
} else {
throw NodeRegistrationException("Compatibility Zone registration service is currently unavailable, "
+ "try again later!.", e)
try {
println("Start polling server for certificate signing approval.")
// Poll server to download the signed certificate once request has been approved.
var idlePeriodDuration: Duration? = null
while (true) {
try {
val (pollInterval, certificates) = certService.retrieveCertificates(requestId)
if (certificates != null) {
return certificates
}
Thread.sleep(pollInterval.toMillis())
} catch (e: ServiceUnavailableException) {
idlePeriodDuration = nextIdleDuration(idlePeriodDuration)
if (idlePeriodDuration != null) {
Thread.sleep(idlePeriodDuration.toMillis())
} else {
throw NodeRegistrationException("Compatibility Zone registration service is currently unavailable, "
+ "try again later!.", e)
}
}
}
} catch (certificateRequestException: CertificateRequestException) {
System.err.println(certificateRequestException.message)
System.err.println("Please make sure the details in configuration file are correct and try again.")
System.err.println("Corda node will now terminate.")
requestIdStore.deleteIfExists()
throw certificateRequestException
}
}
/**
* Submit Certificate Signing Request to Certificate signing service if request ID not found in file system
* Submit Certificate Signing Request to Certificate signing service if request ID not found in file system.
* New request ID will be stored in requestId.txt
* @param keyPair Public Private key pair generated for SSL certification.
* @param publicKey public key for which we need a certificate.
* @param contentSigner the [ContentSigner] that will sign the CSR.
* @return Request ID return from the server.
*/
private fun submitOrResumeCertificateSigningRequest(keyPair: KeyPair): String {
// Retrieve request id from file if exists, else post a request to server.
return if (!requestIdStore.exists()) {
val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, keyPair, certRole)
val writer = StringWriter()
JcaPEMWriter(writer).use {
it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded))
private fun submitOrResumeCertificateSigningRequest(publicKey: PublicKey, contentSigner: ContentSigner): String {
try {
// Retrieve request id from file if exists, else post a request to server.
return if (!requestIdStore.exists()) {
val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, publicKey, contentSigner, certRole)
val writer = StringWriter()
JcaPEMWriter(writer).use {
it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded))
}
println("Certificate signing request with the following information will be submitted to the Corda certificate signing server.")
println()
println("Legal Name: $myLegalName")
println("Email: $emailAddress")
println()
println("Public Key: $publicKey")
println()
println("$writer")
// Post request to signing server via http.
println("Submitting certificate signing request to Corda certificate signing server.")
val requestId = certService.submitRequest(request)
// Persists request ID to file in case of node shutdown.
requestIdStore.writeLines(listOf(requestId))
println("Successfully submitted request to Corda certificate signing server, request ID: $requestId.")
requestId
} else {
val requestId = requestIdStore.readLines { it.findFirst().get() }
println("Resuming from previous certificate signing request, request ID: $requestId.")
requestId
}
println("Certificate signing request with the following information will be submitted to the Corda certificate signing server.")
println()
println("Legal Name: $myLegalName")
println("Email: $emailAddress")
println()
println("Public Key: ${keyPair.public}")
println()
println("$writer")
// Post request to signing server via http.
println("Submitting certificate signing request to Corda certificate signing server.")
val requestId = certService.submitRequest(request)
// Persists request ID to file in case of node shutdown.
requestIdStore.writeLines(listOf(requestId))
println("Successfully submitted request to Corda certificate signing server, request ID: $requestId.")
requestId
} else {
val requestId = requestIdStore.readLines { it.findFirst().get() }
println("Resuming from previous certificate signing request, request ID: $requestId.")
requestId
} catch (e: Exception) {
throw if (e is ConnectException || e is ServiceUnavailableException || e is IOException) {
NodeRegistrationException(e.message, e)
} else e
}
}
protected open fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) {}
protected open fun onSuccess(publicKey: PublicKey, contentSigner: ContentSigner, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) {}
protected open fun validateAndGetTlsCrlIssuerCert(): X509Certificate? = null
@ -256,14 +274,9 @@ class NodeRegistrationException(
class NodeRegistrationHelper(
private val config: NodeConfiguration,
certService: NetworkRegistrationService,
regConfig: NodeRegistrationOption,
computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))
) : NetworkRegistrationHelper(
config.certificatesDirectory,
config.signingCertificateStore,
config.myLegalName,
config.emailAddress,
certService: NetworkRegistrationService, regConfig: NodeRegistrationOption, computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) :
NetworkRegistrationHelper(
config,
certService,
regConfig.networkRootTrustStorePath,
regConfig.networkRootTrustStorePassword,
@ -275,29 +288,38 @@ class NodeRegistrationHelper(
val logger = contextLogger()
}
override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) {
createSSLKeystore(nodeCAKeyPair, certificates, tlsCrlCertificateIssuer)
override fun onSuccess(publicKey: PublicKey, contentSigner: ContentSigner, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) {
createSSLKeystore(publicKey, contentSigner, certificates, tlsCrlCertificateIssuer)
createTruststore(certificates.last())
}
private fun createSSLKeystore(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>, tlsCertCrlIssuer: X500Name?) {
private fun createSSLKeystore(nodeCaPublicKey: PublicKey, nodeCaContentSigner: ContentSigner, nodeCaCertificateChain: List<X509Certificate>, tlsCertCrlIssuer: X500Name?) {
val keyStore = config.p2pSslOptions.keyStore
val certificateStore = keyStore.get(createNew = true)
certificateStore.update {
println("Generating SSL certificate for node messaging service.")
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val issuerCertificate = nodeCaCertificateChain.first()
val validityWindow = X509Utilities.getCertificateValidityWindow(DEFAULT_VALIDITY_WINDOW.first, DEFAULT_VALIDITY_WINDOW.second, issuerCertificate)
val sslCert = X509Utilities.createCertificate(
CertificateType.TLS,
certificates.first(),
nodeCAKeyPair,
issuerCertificate.subjectX500Principal,
nodeCaPublicKey,
nodeCaContentSigner,
config.myLegalName.x500Principal,
sslKeyPair.public,
validityWindow,
crlDistPoint = config.tlsCertCrlDistPoint?.toString(),
crlIssuer = tlsCertCrlIssuer)
logger.info("Generated TLS certificate: $sslCert")
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates, certificateStore.entryPassword)
val sslCertificateChain: List<X509Certificate> = listOf(sslCert) + nodeCaCertificateChain
X509Utilities.validateCertificateChain(rootCert, sslCertificateChain)
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, sslCertificateChain, keyStore.entryPassword)
}
println("SSL private key and certificate stored in ${keyStore.path}.")
println("SSL private key and certificate chain stored in ${keyStore.path}.")
}
private fun createTruststore(rootCertificate: X509Certificate) {
@ -365,4 +387,4 @@ private class FixedPeriodLimitedRetrialStrategy(times: Int, private val period:
return if (counter-- > 0) period else null
}
}
}
}

View File

@ -108,12 +108,11 @@ class NodeConfigurationImplTest {
private fun getConfig(cfgName: String, overrides: Config = ConfigFactory.empty()): Config {
val path = this::class.java.classLoader.getResource(cfgName).toPath()
val cfg = ConfigHelper.loadConfig(
return ConfigHelper.loadConfig(
baseDirectory = path.parent,
configFile = path,
configOverrides = overrides
)
return cfg
}
@Test
@ -177,6 +176,24 @@ class NodeConfigurationImplTest {
}
}
@Test
fun `validation has error on non-null cryptoServiceConf for null cryptoServiceName`() {
val configuration = testConfiguration.copy(cryptoServiceConf = "unsupported.conf")
val errors = configuration.validate()
assertThat(errors).hasOnlyOneElementSatisfying {
error -> error.contains("cryptoServiceName is null, but cryptoServiceConf is set to unsupported.conf")
}
}
@Test
fun `fail on wrong cryptoServiceName`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
rawConfig = rawConfig.withValue("cryptoServiceName", ConfigValueFactory.fromAnyRef("UNSUPPORTED"))
assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.hasMessageStartingWith("UNSUPPORTED is not one of")
}
@Test
fun `rpcAddress and rpcSettings_address are equivalent`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
@ -210,7 +227,7 @@ class NodeConfigurationImplTest {
@Test
fun `jmxReporterType is null and defaults to Jokolia`() {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
val nodeConfig = rawConfig.parseAsNodeConfiguration()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
}

View File

@ -69,6 +69,8 @@ class NetworkRegistrationHelperTest {
doReturn(null).whenever(it).tlsCertCrlDistPoint
doReturn(null).whenever(it).tlsCertCrlIssuer
doReturn(true).whenever(it).crlCheckSoftFail
doReturn(null).whenever(it).cryptoServiceName
doReturn(null).whenever(it).cryptoServiceConf
}
}
@ -85,7 +87,7 @@ class NetworkRegistrationHelperTest {
val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate) }
createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).buildKeystore()
createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).generateKeysAndRegister()
val nodeKeystore = config.signingCertificateStore.get()
val sslKeystore = config.p2pSslOptions.keyStore.get()
@ -130,7 +132,7 @@ class NetworkRegistrationHelperTest {
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java)
.isThrownBy { registrationHelper.buildKeystore() }
.isThrownBy { registrationHelper.generateKeysAndRegister() }
.withMessageContaining(CertificateType.TLS.toString())
}
@ -141,7 +143,7 @@ class NetworkRegistrationHelperTest {
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java)
.isThrownBy { registrationHelper.buildKeystore() }
.isThrownBy { registrationHelper.generateKeysAndRegister() }
.withMessageContaining(invalidName.toString())
}
@ -156,7 +158,7 @@ class NetworkRegistrationHelperTest {
}
val registrationHelper = createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA)
registrationHelper.buildKeystore()
registrationHelper.generateKeysAndRegister()
val trustStore = config.p2pSslOptions.trustStore.get()
trustStore.run {
assertTrue(contains(extraTrustedCertAlias))
@ -174,7 +176,7 @@ class NetworkRegistrationHelperTest {
val registrationHelper = createRegistrationHelper()
assertThatThrownBy {
registrationHelper.buildKeystore()
registrationHelper.generateKeysAndRegister()
}.isInstanceOf(CertPathValidatorException::class.java)
}
@ -186,7 +188,7 @@ class NetworkRegistrationHelperTest {
val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate) }
createRegistrationHelper(CertRole.SERVICE_IDENTITY, rootAndIntermediateCA).buildKeystore()
createRegistrationHelper(CertRole.SERVICE_IDENTITY, rootAndIntermediateCA).generateKeysAndRegister()
val nodeKeystore = config.signingCertificateStore.get()
@ -251,10 +253,7 @@ class NetworkRegistrationHelperTest {
return when (certRole) {
CertRole.NODE_CA -> NodeRegistrationHelper(config, certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword))
CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper(
config.certificatesDirectory,
config.signingCertificateStore,
config.myLegalName,
config.emailAddress,
config,
certService,
config.certificatesDirectory / networkRootTrustStoreFileName,
networkRootTrustStorePassword,