Replaced KeyStoreWrapper with X509KeyStore, which is still a wrapper but assumes only X509 certs and has better APIs (#2411)

This commit is contained in:
Shams Asari 2018-01-24 07:51:55 +00:00 committed by GitHub
parent 5df50c0e81
commit 61c7de22d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 330 additions and 317 deletions

View File

@ -1,6 +1,7 @@
package net.corda.core.internal package net.corda.core.internal
import net.corda.core.CordaOID import net.corda.core.CordaOID
import net.corda.core.utilities.NonEmptySet
import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Primitive import org.bouncycastle.asn1.ASN1Primitive
@ -23,40 +24,38 @@ import java.security.cert.X509Certificate
// NOTE: The order of the entries in the enum MUST NOT be changed, as their ordinality is used as an identifier. Please // NOTE: The order of the entries in the enum MUST NOT be changed, as their ordinality is used as an identifier. Please
// also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications. // also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications.
// TODO: Link to the specification once it has a permanent URL // TODO: Link to the specification once it has a permanent URL
enum class CertRole(val validParents: Set<CertRole?>, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable { enum class CertRole(val validParents: NonEmptySet<CertRole?>, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable {
/** /**
* Intermediate CA (Doorman service). * Intermediate CA (Doorman service).
*/ */
INTERMEDIATE_CA(setOf(null), false, false), INTERMEDIATE_CA(NonEmptySet.of(null), false, false),
/** Signing certificate for the network map. */ /** Signing certificate for the network map. */
NETWORK_MAP(setOf(null), false, false), NETWORK_MAP(NonEmptySet.of(null), false, false),
/** Well known (publicly visible) identity of a service (such as notary). */ /** Well known (publicly visible) identity of a service (such as notary). */
SERVICE_IDENTITY(setOf(INTERMEDIATE_CA), true, true), SERVICE_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA), true, true),
/** Node level CA from which the TLS and well known identity certificates are issued. */ /** Node level CA from which the TLS and well known identity certificates are issued. */
NODE_CA(setOf(INTERMEDIATE_CA), false, false), NODE_CA(NonEmptySet.of(INTERMEDIATE_CA), false, false),
/** Transport layer security certificate for a node. */ /** Transport layer security certificate for a node. */
TLS(setOf(NODE_CA), false, false), TLS(NonEmptySet.of(NODE_CA), false, false),
/** Well known (publicly visible) identity of a legal entity. */ /** Well known (publicly visible) identity of a legal entity. */
LEGAL_IDENTITY(setOf(INTERMEDIATE_CA, NODE_CA), true, true), LEGAL_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA, NODE_CA), true, true),
/** Confidential (limited visibility) identity of a legal entity. */ /** Confidential (limited visibility) identity of a legal entity. */
CONFIDENTIAL_LEGAL_IDENTITY(setOf(LEGAL_IDENTITY), true, false); CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false);
companion object { companion object {
private var cachedRoles: Array<CertRole>? = null private val values by lazy(LazyThreadSafetyMode.NONE, CertRole::values)
/** /**
* Get a role from its ASN.1 encoded form. * Get a role from its ASN.1 encoded form.
* *
* @throws IllegalArgumentException if the encoded data is not a valid role. * @throws IllegalArgumentException if the encoded data is not a valid role.
*/ */
fun getInstance(id: ASN1Integer): CertRole { fun getInstance(id: ASN1Integer): CertRole {
if (cachedRoles == null) {
cachedRoles = CertRole.values()
}
val idVal = id.value val idVal = id.value
require(idVal.compareTo(BigInteger.ZERO) > 0) { "Invalid role ID" } require(idVal > BigInteger.ZERO) { "Invalid role ID" }
return try { return try {
val ordinal = idVal.intValueExact() - 1 val ordinal = idVal.intValueExact() - 1
cachedRoles!![ordinal] values[ordinal]
} catch (ex: ArithmeticException) { } catch (ex: ArithmeticException) {
throw IllegalArgumentException("Invalid role ID") throw IllegalArgumentException("Invalid role ID")
} catch (ex: ArrayIndexOutOfBoundsException) { } catch (ex: ArrayIndexOutOfBoundsException) {
@ -77,14 +76,7 @@ enum class CertRole(val validParents: Set<CertRole?>, val isIdentity: Boolean, v
* @return the role if the extension is present, or null otherwise. * @return the role if the extension is present, or null otherwise.
* @throws IllegalArgumentException if the extension is present but is invalid. * @throws IllegalArgumentException if the extension is present but is invalid.
*/ */
fun extract(cert: Certificate): CertRole? { fun extract(cert: Certificate): CertRole? = (cert as? X509Certificate)?.let { extract(it) }
val x509Cert = cert as? X509Certificate
return if (x509Cert != null) {
extract(x509Cert)
} else {
null
}
}
/** /**
* Get a role from a certificate. * Get a role from a certificate.
@ -93,12 +85,9 @@ enum class CertRole(val validParents: Set<CertRole?>, val isIdentity: Boolean, v
* @throws IllegalArgumentException if the extension is present but is invalid. * @throws IllegalArgumentException if the extension is present but is invalid.
*/ */
fun extract(cert: X509Certificate): CertRole? { fun extract(cert: X509Certificate): CertRole? {
val extensionData: ByteArray? = cert.getExtensionValue(CordaOID.X509_EXTENSION_CORDA_ROLE) return cert.getExtensionValue(CordaOID.X509_EXTENSION_CORDA_ROLE)?.let {
return if (extensionData != null) { val extensionString = DEROctetString.getInstance(it)
val extensionString = DEROctetString.getInstance(extensionData)
getInstance(extensionString.octets) getInstance(extensionString.octets)
} else {
null
} }
} }
} }

View File

@ -6,9 +6,13 @@ import net.corda.core.internal.div
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58String import net.corda.core.utilities.toBase58String
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.kryoSpecific import net.corda.testing.internal.kryoSpecific
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
@ -341,9 +345,9 @@ class CompositeKeyTests {
// Store certificate to keystore. // Store certificate to keystore.
val keystorePath = tempFolder.root.toPath() / "keystore.jks" val keystorePath = tempFolder.root.toPath() / "keystore.jks"
val keystore = loadOrCreateKeyStore(keystorePath, "password") X509KeyStore.fromFile(keystorePath, "password", createNew = true).update {
keystore.setCertificateEntry("CompositeKey", compositeKeyCert) setCertificate("CompositeKey", compositeKeyCert)
keystore.save(keystorePath, "password") }
// Load keystore from disk. // Load keystore from disk.
val keystore2 = loadKeyStore(keystorePath, "password") val keystore2 = loadKeyStore(keystorePath, "password")
@ -352,7 +356,7 @@ class CompositeKeyTests {
val key = keystore2.getCertificate("CompositeKey").publicKey val key = keystore2.getCertificate("CompositeKey").publicKey
// Convert sun public key to Composite key. // Convert sun public key to Composite key.
val compositeKey2 = Crypto.toSupportedPublicKey(key) val compositeKey2 = Crypto.toSupportedPublicKey(key)
assertTrue { compositeKey2 is CompositeKey } assertThat(compositeKey2).isInstanceOf(CompositeKey::class.java)
// Run the same composite key test again. // Run the same composite key test again.
assertTrue { compositeKey2.isFulfilledBy(signatures.byKeys()) } assertTrue { compositeKey2.isFulfilledBy(signatures.byKeys()) }

View File

@ -1,21 +1,19 @@
package net.corda.core.identity package net.corda.core.identity
import com.google.common.jimfs.Configuration.unix
import com.google.common.jimfs.Jimfs
import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.internal.read
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.KEYSTORE_TYPE
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.save import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.DEV_ROOT_CA
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.getTestPartyAndCertificate import net.corda.testing.core.getTestPartyAndCertificate
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.io.File
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyStore
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class PartyAndCertificateTest { class PartyAndCertificateTest {
@ -46,17 +44,18 @@ class PartyAndCertificateTest {
CordaX500Name(organisation = "Test Corp", locality = "Madrid", country = "ES"), CordaX500Name(organisation = "Test Corp", locality = "Madrid", country = "ES"),
entropyToKeyPair(BigInteger.valueOf(83)).public)) entropyToKeyPair(BigInteger.valueOf(83)).public))
val original = identity.certificate val original = identity.certificate
val alias = identity.name.toString()
val storePassword = "test" val storePassword = "test"
val keyStoreFilePath = File.createTempFile("serialization_test", "jks").toPath() Jimfs.newFileSystem(unix()).use {
var keyStore = KeyStore.getInstance(KEYSTORE_TYPE) val keyStoreFile = it.getPath("/serialization_test.jks")
keyStore.load(null, storePassword.toCharArray())
keyStore.setCertificateEntry(identity.name.toString(), original) X509KeyStore.fromFile(keyStoreFile, storePassword, createNew = true).update {
keyStore.save(keyStoreFilePath, storePassword) setCertificate(alias, original)
}
// Load the key store back in again // Load the key store back in again
keyStore = KeyStore.getInstance(KEYSTORE_TYPE) val copy = X509KeyStore.fromFile(keyStoreFile, storePassword).getCertificate(alias)
keyStoreFilePath.read { keyStore.load(it, storePassword.toCharArray()) } assertThat(copy).isEqualTo(original)
val copy = keyStore.getCertificate(identity.name.toString()) }
assertThat(copy).isEqualTo(original) // .isNotSameAs(original)
} }
} }

View File

@ -1,7 +1,6 @@
package net.corda.nodeapi.internal package net.corda.nodeapi.internal
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
@ -9,7 +8,9 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.NodeSSLConfiguration
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
@ -37,10 +38,9 @@ object DevIdentityGenerator {
} }
nodeSslConfig.certificatesDirectory.createDirectories() nodeSslConfig.certificatesDirectory.createDirectories()
nodeSslConfig.createDevKeyStores(legalName) val (nodeKeyStore) = nodeSslConfig.createDevKeyStores(legalName)
val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword) val identity = nodeKeyStore.storeLegalIdentity("$NODE_IDENTITY_ALIAS_PREFIX-private-key")
val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair())
return identity.party return identity.party
} }
@ -78,13 +78,13 @@ object DevIdentityGenerator {
publicKey) publicKey)
} }
val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks" val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks"
val keystore = loadOrCreateKeyStore(distServKeyStoreFile, "cordacadevpass") X509KeyStore.fromFile(distServKeyStoreFile, "cordacadevpass", createNew = true).update {
keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert)
keystore.setKeyEntry( setPrivateKey(
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
keyPair.private, keyPair.private,
"cordacadevkeypass".toCharArray(), listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate),
arrayOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) "cordacadevkeypass")
keystore.save(distServKeyStoreFile, "cordacadevpass") }
} }
} }

View File

@ -1,7 +1,9 @@
package net.corda.nodeapi.internal package net.corda.nodeapi.internal
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.x500Name import net.corda.core.internal.x500Name
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.*
@ -20,50 +22,47 @@ import javax.security.auth.x500.X500Principal
*/ */
fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name, fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name,
rootCert: X509Certificate = DEV_ROOT_CA.certificate, rootCert: X509Certificate = DEV_ROOT_CA.certificate,
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) { intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA): Pair<X509KeyStore, X509KeyStore> {
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName) val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
createDevKeyStores(rootCert, intermediateCa, nodeCaCert, nodeCaKeyPair, legalName) val nodeKeyStore = loadNodeKeyStore(createNew = true)
} nodeKeyStore.update {
setPrivateKey(
/**
* Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
*/
fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateCa: CertificateAndKeyPair, nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, legalName: CordaX500Name) {
createNodeKeyStore(nodeCaCert, nodeCaKeyPair, intermediateCa, rootCert)
createSslKeyStore(nodeCaCert, nodeCaKeyPair, legalName, intermediateCa, rootCert)
}
/**
* Create the SSL key store needed by a node.
*/
fun SSLConfiguration.createSslKeyStore(nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, legalName: CordaX500Name, intermediateCa: CertificateAndKeyPair, rootCert: X509Certificate) {
val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public)
loadOrCreateKeyStore(sslKeystore, keyStorePassword).apply {
addOrReplaceKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
keyStorePassword.toCharArray(),
arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert))
save(sslKeystore, keyStorePassword)
}
}
/**
* Create the node key store needed by a node.
*/
fun SSLConfiguration.createNodeKeyStore(nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, intermediateCa: CertificateAndKeyPair, rootCert: X509Certificate) {
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
addOrReplaceKey(
X509Utilities.CORDA_CLIENT_CA, X509Utilities.CORDA_CLIENT_CA,
nodeCaKeyPair.private, nodeCaKeyPair.private,
keyStorePassword.toCharArray(), listOf(nodeCaCert, intermediateCa.certificate, rootCert))
arrayOf(nodeCaCert, intermediateCa.certificate, rootCert))
save(nodeKeystore, keyStorePassword)
} }
val sslKeyStore = loadSslKeyStore(createNew = true)
sslKeyStore.update {
val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public)
setPrivateKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
listOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert))
}
return Pair(nodeKeyStore, sslKeyStore)
}
fun X509KeyStore.storeLegalIdentity(alias: String, keyPair: KeyPair = Crypto.generateKeyPair()): PartyAndCertificate {
val nodeCaCertPath = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
// Assume key password = store password.
val nodeCaCertAndKeyPair = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
// Create new keys and store in keystore.
val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY,
nodeCaCertAndKeyPair.certificate,
nodeCaCertAndKeyPair.keyPair,
nodeCaCertAndKeyPair.certificate.subjectX500Principal,
keyPair.public)
// TODO: X509Utilities.validateCertificateChain()
// Assume key password = store password.
val identityCertPath = listOf(identityCert) + nodeCaCertPath
setPrivateKey(alias, keyPair.private, identityCertPath)
save()
return PartyAndCertificate(X509CertificateFactory().generateCertPath(identityCertPath))
} }
fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair { fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair {

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.config package net.corda.nodeapi.internal.config
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.nodeapi.internal.crypto.X509KeyStore
import java.nio.file.Path import java.nio.file.Path
interface SSLConfiguration { interface SSLConfiguration {
@ -11,6 +12,18 @@ interface SSLConfiguration {
// TODO This looks like it should be in NodeSSLConfiguration // TODO This looks like it should be in NodeSSLConfiguration
val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks" val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks"
val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks" val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks"
fun loadTrustStore(createNew: Boolean = false): X509KeyStore {
return X509KeyStore.fromFile(trustStoreFile, trustStorePassword, createNew)
}
fun loadNodeKeyStore(createNew: Boolean = false): X509KeyStore {
return X509KeyStore.fromFile(nodeKeystore, keyStorePassword, createNew)
}
fun loadSslKeyStore(createNew: Boolean = false): X509KeyStore {
return X509KeyStore.fromFile(sslKeystore, keyStorePassword, createNew)
}
} }
interface NodeSSLConfiguration : SSLConfiguration { interface NodeSSLConfiguration : SSLConfiguration {

View File

@ -9,7 +9,6 @@ import net.corda.core.internal.read
import net.corda.core.internal.write import net.corda.core.internal.write
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Path import java.nio.file.Path
import java.security.* import java.security.*
import java.security.cert.Certificate import java.security.cert.Certificate
@ -103,17 +102,11 @@ fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) {
* @param storePassword password to access the store in future. This does not have to be the same password as any keys stored, * @param storePassword password to access the store in future. This does not have to be the same password as any keys stored,
* but for SSL purposes this is recommended. * but for SSL purposes this is recommended.
*/ */
fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) = keyStoreFilePath.write { store(it, storePassword) } fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) {
keyStoreFilePath.write {
fun KeyStore.store(out: OutputStream, password: String) = store(out, password.toCharArray()) store(it, storePassword.toCharArray())
}
/** }
* Extract public and private keys from a KeyStore file assuming storage alias is known.
* @param alias The name to lookup the Key and Certificate chain from.
* @param keyPassword Password to unlock the private key entries.
* @return The KeyPair found in the KeyStore under the specified alias.
*/
fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertificateAndKeyPair(alias, keyPassword).keyPair
/** /**
* Helper method to load a Certificate and KeyPair from their KeyStore. * Helper method to load a Certificate and KeyPair from their KeyStore.
@ -135,7 +128,7 @@ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): Certi
*/ */
fun KeyStore.getX509Certificate(alias: String): X509Certificate { fun KeyStore.getX509Certificate(alias: String): X509Certificate {
val certificate = getCertificate(alias) ?: throw IllegalArgumentException("No certificate under alias \"$alias\".") val certificate = getCertificate(alias) ?: throw IllegalArgumentException("No certificate under alias \"$alias\".")
return certificate as? X509Certificate ?: throw IllegalArgumentException("Certificate under alias \"$alias\" is not an X.509 certificate.") return certificate as? X509Certificate ?: throw IllegalStateException("Certificate under alias \"$alias\" is not an X.509 certificate: $certificate")
} }
/** /**

View File

@ -1,40 +0,0 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.read
import java.nio.file.Path
import java.security.KeyPair
import java.security.cert.Certificate
class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) {
private val keyStore = storePath.read { loadKeyStore(it, storePassword) }
// TODO This method seems misplaced in this class.
fun storeLegalIdentity(legalName: CordaX500Name, alias: String, keyPair: KeyPair): PartyAndCertificate {
val nodeCaCertChain = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
val nodeCa = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY,
nodeCa.certificate,
nodeCa.keyPair,
legalName.x500Principal,
keyPair.public)
val identityCertPath = X509CertificateFactory().generateCertPath(identityCert, *nodeCaCertChain)
// Assume key password = store password.
keyStore.addOrReplaceKey(alias, keyPair.private, storePassword.toCharArray(), identityCertPath.certificates.toTypedArray())
keyStore.save(storePath, storePassword)
return PartyAndCertificate(identityCertPath)
}
// Delegate methods to keystore. Sadly keystore doesn't have an interface.
fun containsAlias(alias: String) = keyStore.containsAlias(alias)
fun getX509Certificate(alias: String) = keyStore.getX509Certificate(alias)
fun getCertificateChain(alias: String): Array<out Certificate> = keyStore.getCertificateChain(alias)
fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias)
fun getCertificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword)
}

View File

@ -0,0 +1,74 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto
import net.corda.core.internal.uncheckedCast
import java.nio.file.Path
import java.security.KeyPair
import java.security.KeyStore
import java.security.PrivateKey
import java.security.cert.X509Certificate
/**
* Wrapper around a [KeyStore] object but only dealing with [X509Certificate]s and with a better API.
*/
class X509KeyStore private constructor(val internal: KeyStore, private val storePassword: String, private val keyStoreFile: Path? = null) {
/** Wrap an existing [KeyStore]. [save] is not supported. */
constructor(internal: KeyStore, storePassword: String) : this(internal, storePassword, null)
companion object {
/**
* Read a [KeyStore] from the given file. If the key store doesn't exist and [createNew] is true then a blank
* key store will be written out. Changes to the returned [X509KeyStore] can be persisted with [save].
*/
fun fromFile(keyStoreFile: Path, storePassword: String, createNew: Boolean = false): X509KeyStore {
val internal: KeyStore = if (createNew) loadOrCreateKeyStore(keyStoreFile, storePassword) else loadKeyStore(keyStoreFile, storePassword)
return X509KeyStore(internal, storePassword, keyStoreFile)
}
}
operator fun contains(alias: String): Boolean = internal.containsAlias(alias)
fun aliases(): Iterator<String> = internal.aliases().iterator()
fun getCertificate(alias: String): X509Certificate = internal.getX509Certificate(alias)
fun getCertificateChain(alias: String): List<X509Certificate> {
val certArray = requireNotNull(internal.getCertificateChain(alias)) { "No certificate chain under the alias $alias" }
check(certArray.all { it is X509Certificate }) { "Certificate chain under alias $alias is not X.509" }
return uncheckedCast(certArray.asList())
}
fun getCertificateAndKeyPair(alias: String, keyPassword: String = storePassword): CertificateAndKeyPair {
val cert = getCertificate(alias)
val publicKey = Crypto.toSupportedPublicKey(cert.publicKey)
return CertificateAndKeyPair(cert, KeyPair(publicKey, getPrivateKey(alias, keyPassword)))
}
fun getPrivateKey(alias: String, keyPassword: String = storePassword): PrivateKey {
return internal.getSupportedKey(alias, keyPassword)
}
fun setPrivateKey(alias: String, key: PrivateKey, certificates: List<X509Certificate>, keyPassword: String = storePassword) {
checkWritableToFile()
internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray())
}
fun setCertificate(alias: String, certificate: X509Certificate) {
checkWritableToFile()
internal.setCertificateEntry(alias, certificate)
}
fun save() {
internal.save(checkWritableToFile(), storePassword)
}
fun update(action: X509KeyStore.() -> Unit) {
checkWritableToFile()
action(this)
save()
}
private fun checkWritableToFile(): Path {
return keyStoreFile ?: throw IllegalStateException("This key store cannot be written to")
}
}

View File

@ -7,8 +7,6 @@ import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.CertRole import net.corda.core.internal.CertRole
import net.corda.core.internal.reader import net.corda.core.internal.reader
import net.corda.core.internal.writer import net.corda.core.internal.writer
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import org.bouncycastle.asn1.* import org.bouncycastle.asn1.*
@ -95,6 +93,7 @@ object X509Utilities {
return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window) return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
} }
// TODO Provide an overload which takes in a List or a CertPath
@Throws(CertPathValidatorException::class) @Throws(CertPathValidatorException::class)
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) { fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
@ -287,10 +286,12 @@ class X509CertificateFactory {
return delegate.generateCertificate(input) as X509Certificate return delegate.generateCertificate(input) as X509Certificate
} }
// TODO X509Certificate
fun generateCertPath(certificates: List<Certificate>): CertPath { fun generateCertPath(certificates: List<Certificate>): CertPath {
return delegate.generateCertPath(certificates) return delegate.generateCertPath(certificates)
} }
// TODO X509Certificate
fun generateCertPath(vararg certificates: Certificate): CertPath { fun generateCertPath(vararg certificates: Certificate): CertPath {
return delegate.generateCertPath(certificates.asList()) return delegate.generateCertPath(certificates.asList())
} }

View File

@ -222,7 +222,8 @@ class X509UtilitiesTest {
val clientSocketFactory = context.socketFactory val clientSocketFactory = context.socketFactory
val serverSocket = serverSocketFactory.createServerSocket(0) as SSLServerSocket // use 0 to get first free socket val serverSocket = serverSocketFactory.createServerSocket(0) as SSLServerSocket // use 0 to get first free socket
val serverParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2")) val serverParams = SSLParameters(CIPHER_SUITES,
arrayOf("TLSv1.2"))
serverParams.wantClientAuth = true serverParams.wantClientAuth = true
serverParams.needClientAuth = true serverParams.needClientAuth = true
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
@ -230,7 +231,8 @@ class X509UtilitiesTest {
serverSocket.useClientMode = false serverSocket.useClientMode = false
val clientSocket = clientSocketFactory.createSocket() as SSLSocket val clientSocket = clientSocketFactory.createSocket() as SSLSocket
val clientParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2")) val clientParams = SSLParameters(CIPHER_SUITES,
arrayOf("TLSv1.2"))
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
clientSocket.sslParameters = clientParams clientSocket.sslParameters = clientParams
clientSocket.useClientMode = true clientSocket.useClientMode = true
@ -267,7 +269,7 @@ class X509UtilitiesTest {
val peerChain = clientSocket.session.peerCertificates val peerChain = clientSocket.session.peerCertificates
val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal) assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal)
X509Utilities.validateCertificateChain(trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), *peerChain) X509Utilities.validateCertificateChain(rootCa.certificate, *peerChain)
val output = DataOutputStream(clientSocket.outputStream) val output = DataOutputStream(clientSocket.outputStream)
output.writeUTF("Hello World") output.writeUTF("Hello World")
var timeout = 0 var timeout = 0

View File

@ -5,7 +5,8 @@ import net.corda.core.internal.div
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
@ -45,15 +46,14 @@ class NodeKeystoreCheckTest {
node.stop() node.stop()
// Fiddle with node keystore. // Fiddle with node keystore.
val keystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) config.loadNodeKeyStore().update {
// Self signed root // Self signed root
val badRootKeyPair = Crypto.generateKeyPair() val badRootKeyPair = Crypto.generateKeyPair()
val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair) val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair)
val nodeCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword) val nodeCA = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public) val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public)
keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert, badRoot)) setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, listOf(badNodeCACert, badRoot))
keystore.save(config.nodeKeystore, config.keyStorePassword) }
assertThatThrownBy { assertThatThrownBy {
startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow()

View File

@ -15,7 +15,6 @@ import net.corda.node.services.config.*
import net.corda.node.services.messaging.ArtemisMessagingClient import net.corda.node.services.messaging.ArtemisMessagingClient
import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
@ -229,16 +228,14 @@ class AMQPBridgeTest {
} }
serverConfig.configureWithDevSSLCertificate() serverConfig.configureWithDevSSLCertificate()
val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword) return AMQPServer("0.0.0.0",
val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
val amqpServer = AMQPServer("0.0.0.0",
amqpPort, amqpPort,
ArtemisMessagingComponent.PEER_USER, ArtemisMessagingComponent.PEER_USER,
ArtemisMessagingComponent.PEER_USER, ArtemisMessagingComponent.PEER_USER,
serverKeystore, serverConfig.loadSslKeyStore().internal,
serverConfig.keyStorePassword, serverConfig.keyStorePassword,
serverTruststore, serverConfig.loadTrustStore().internal,
trace = true) trace = true
return amqpServer )
} }
} }

View File

@ -21,7 +21,6 @@ import net.corda.node.services.messaging.ArtemisMessagingClient
import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.RoutingType
@ -253,8 +252,8 @@ class ProtonWrapperTests {
} }
clientConfig.configureWithDevSSLCertificate() clientConfig.configureWithDevSSLCertificate()
val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword) val clientTruststore = clientConfig.loadTrustStore().internal
val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword) val clientKeystore = clientConfig.loadSslKeyStore().internal
return AMQPClient( return AMQPClient(
listOf(NetworkHostAndPort("localhost", serverPort), listOf(NetworkHostAndPort("localhost", serverPort),
NetworkHostAndPort("localhost", serverPort2), NetworkHostAndPort("localhost", serverPort2),
@ -276,8 +275,8 @@ class ProtonWrapperTests {
} }
clientConfig.configureWithDevSSLCertificate() clientConfig.configureWithDevSSLCertificate()
val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword) val clientTruststore = clientConfig.loadTrustStore().internal
val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword) val clientKeystore = clientConfig.loadSslKeyStore().internal
return AMQPClient( return AMQPClient(
listOf(NetworkHostAndPort("localhost", serverPort)), listOf(NetworkHostAndPort("localhost", serverPort)),
setOf(ALICE_NAME), setOf(ALICE_NAME),
@ -297,8 +296,8 @@ class ProtonWrapperTests {
} }
serverConfig.configureWithDevSSLCertificate() serverConfig.configureWithDevSSLCertificate()
val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword) val serverTruststore = serverConfig.loadTrustStore().internal
val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword) val serverKeystore = serverConfig.loadSslKeyStore().internal
return AMQPServer( return AMQPServer(
"0.0.0.0", "0.0.0.0",
port, port,

View File

@ -12,7 +12,8 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
import net.corda.nodeapi.internal.DEV_ROOT_CA import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
@ -115,22 +116,19 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
CordaX500Name("MiniCorp", "London", "GB").x500Principal, CordaX500Name("MiniCorp", "London", "GB").x500Principal,
tlsKeyPair.public) tlsKeyPair.public)
val keyPass = keyStorePassword.toCharArray() loadNodeKeyStore(createNew = true).update {
val clientCAKeystore = loadOrCreateKeyStore(nodeKeystore, keyStorePassword) setPrivateKey(
clientCAKeystore.addOrReplaceKey(
X509Utilities.CORDA_CLIENT_CA, X509Utilities.CORDA_CLIENT_CA,
clientKeyPair.private, clientKeyPair.private,
keyPass, listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
arrayOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) }
clientCAKeystore.save(nodeKeystore, keyStorePassword)
val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword) loadSslKeyStore(createNew = true).update {
tlsKeystore.addOrReplaceKey( setPrivateKey(
X509Utilities.CORDA_CLIENT_TLS, X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private, tlsKeyPair.private,
keyPass, listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
arrayOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) }
tlsKeystore.save(sslKeystore, keyStorePassword)
} }
} }

View File

@ -36,7 +36,10 @@ import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.api.* import net.corda.node.services.api.*
import net.corda.node.services.config.* import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
@ -55,16 +58,15 @@ import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.KeyStoreWrapper
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.nodeapi.internal.storeLegalIdentity
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
import org.slf4j.Logger import org.slf4j.Logger
@ -140,7 +142,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
private val _nodeReadyFuture = openFuture<Unit>() private val _nodeReadyFuture = openFuture<Unit>()
protected var networkMapClient: NetworkMapClient? = null protected var networkMapClient: NetworkMapClient? = null
lateinit var securityManager: RPCSecurityManager get lateinit var securityManager: RPCSecurityManager
/** Completes once the node has successfully registered with the network map service /** Completes once the node has successfully registered with the network map service
* or has loaded network map data from local database */ * or has loaded network map data from local database */
@ -568,9 +570,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
private fun validateKeystore() { private fun validateKeystore() {
val containCorrectKeys = try { val containCorrectKeys = try {
// This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect. // This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) val sslKeystore = configuration.loadSslKeyStore()
val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword) val identitiesKeystore = configuration.loadNodeKeyStore()
sslKeystore.containsAlias(X509Utilities.CORDA_CLIENT_TLS) && identitiesKeystore.containsAlias(X509Utilities.CORDA_CLIENT_CA) X509Utilities.CORDA_CLIENT_TLS in sslKeystore && X509Utilities.CORDA_CLIENT_CA in identitiesKeystore
} catch (e: KeyStoreException) { } catch (e: KeyStoreException) {
log.warn("Certificate key store found but key store password does not match configuration.") log.warn("Certificate key store found but key store password does not match configuration.")
false false
@ -585,15 +587,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} }
// Check all cert path chain to the trusted root // Check all cert path chain to the trusted root
val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) val sslCertChainRoot = configuration.loadSslKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last()
val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword) val nodeCaCertChainRoot = configuration.loadNodeKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last()
val trustStore = loadKeyStore(configuration.trustStoreFile, configuration.trustStorePassword) val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
val sslRoot = sslKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last()
val clientCARoot = identitiesKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last()
val trustRoot = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
require(sslRoot == trustRoot) { "TLS certificate must chain to the trusted root." } require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
require(clientCARoot == trustRoot) { "Client CA certificate must chain to the trusted root." } require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
} }
// Specific class so that MockNode can catch it. // Specific class so that MockNode can catch it.
@ -684,12 +683,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} }
private fun makeIdentityService(identityCert: X509Certificate): PersistentIdentityService { private fun makeIdentityService(identityCert: X509Certificate): PersistentIdentityService {
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA)
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) return PersistentIdentityService(trustRoot, identityCert, nodeCa)
val clientCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val caCertificates = arrayOf(identityCert, clientCa.certificate)
return PersistentIdentityService(trustRoot, *caCertificates)
} }
protected abstract fun makeTransactionVerifierService(): TransactionVerifierService protected abstract fun makeTransactionVerifierService(): TransactionVerifierService
@ -713,7 +709,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
protected abstract fun startMessagingService(rpcOps: RPCOps) protected abstract fun startMessagingService(rpcOps: RPCOps)
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> { private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val keyStore = configuration.loadNodeKeyStore()
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
// Node's main identity or if it's a single node notary // Node's main identity or if it's a single node notary
@ -725,19 +721,20 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
// TODO: Integrate with Key management service? // TODO: Integrate with Key management service?
val privateKeyAlias = "$id-private-key" val privateKeyAlias = "$id-private-key"
if (!keyStore.containsAlias(privateKeyAlias)) { if (privateKeyAlias !in keyStore) {
singleName ?: throw IllegalArgumentException( singleName ?: throw IllegalArgumentException(
"Unable to find in the key store the identity of the distributed notary ($id) the node is part of") "Unable to find in the key store the identity of the distributed notary the node is part of")
// TODO: Remove use of [IdentityGenerator.generateToDisk].
log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!") log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!")
keyStore.storeLegalIdentity(singleName, privateKeyAlias, generateKeyPair()) // TODO This check shouldn't be needed
check(singleName == configuration.myLegalName)
keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair())
} }
val (x509Cert, keyPair) = keyStore.getCertificateAndKeyPair(privateKeyAlias) val (x509Cert, keyPair) = keyStore.getCertificateAndKeyPair(privateKeyAlias)
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity. // TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val compositeKeyAlias = "$id-composite-key" val compositeKeyAlias = "$id-composite-key"
val certificates = if (keyStore.containsAlias(compositeKeyAlias)) { val certificates = if (compositeKeyAlias in keyStore) {
// Use composite key instead if it exists // Use composite key instead if it exists
val certificate = keyStore.getCertificate(compositeKeyAlias) val certificate = keyStore.getCertificate(compositeKeyAlias)
// We have to create the certificate chain for the composite key manually, this is because we don't have a keystore // We have to create the certificate chain for the composite key manually, this is because we don't have a keystore
@ -747,12 +744,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} else { } else {
keyStore.getCertificateChain(privateKeyAlias).let { keyStore.getCertificateChain(privateKeyAlias).let {
check(it[0] == x509Cert) { "Certificates from key store do not line up!" } check(it[0] == x509Cert) { "Certificates from key store do not line up!" }
it.asList() it
} }
} }
val nodeCert = certificates[0] as? X509Certificate ?: throw ConfigurationException("Node certificate must be an X.509 certificate") val subject = CordaX500Name.build(certificates[0].subjectX500Principal)
val subject = CordaX500Name.build(nodeCert.subjectX500Principal)
// TODO Include the name of the distributed notary, which the node is part of, in the notary config so that we // TODO Include the name of the distributed notary, which the node is part of, in the notary config so that we
// can cross-check the identity we get from the key store // can cross-check the identity we get from the key store
if (singleName != null && subject != singleName) { if (singleName != null && subject != singleName) {

View File

@ -10,7 +10,9 @@ import net.corda.core.internal.div
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.createDevKeyStores import net.corda.nodeapi.internal.createDevKeyStores
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.nodeapi.internal.crypto.save
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.nio.file.Path import java.nio.file.Path
@ -52,22 +54,21 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword) loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
} }
if (!sslKeystore.exists() || !nodeKeystore.exists()) { if (!sslKeystore.exists() || !nodeKeystore.exists()) {
createDevKeyStores(myLegalName) val (nodeKeyStore) = createDevKeyStores(myLegalName)
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists. // Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks" val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
if (distributedServiceKeystore.exists()) { if (distributedServiceKeystore.exists()) {
val serviceKeystore = loadKeyStore(distributedServiceKeystore, "cordacadevpass") val serviceKeystore = X509KeyStore.fromFile(distributedServiceKeystore, "cordacadevpass")
val cordaNodeKeystore = loadKeyStore(nodeKeystore, keyStorePassword) nodeKeyStore.update {
serviceKeystore.aliases().forEach {
serviceKeystore.aliases().iterator().forEach { if (serviceKeystore.internal.isKeyEntry(it)) {
if (serviceKeystore.isKeyEntry(it)) { setPrivateKey(it, serviceKeystore.getPrivateKey(it, "cordacadevkeypass"), serviceKeystore.getCertificateChain(it))
cordaNodeKeystore.setKeyEntry(it, serviceKeystore.getKey(it, "cordacadevkeypass".toCharArray()), keyStorePassword.toCharArray(), serviceKeystore.getCertificateChain(it))
} else { } else {
cordaNodeKeystore.setCertificateEntry(it, serviceKeystore.getCertificate(it)) setCertificate(it, serviceKeystore.getCertificate(it))
}
} }
} }
cordaNodeKeystore.save(nodeKeystore, keyStorePassword)
} }
} }
} }

View File

@ -14,7 +14,6 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
import net.corda.nodeapi.internal.crypto.loadKeyStore
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
import org.apache.activemq.artemis.api.core.client.ClientConsumer import org.apache.activemq.artemis.api.core.client.ClientConsumer
@ -38,9 +37,9 @@ internal class AMQPBridgeManager(val config: NodeConfiguration, val p2pAddress:
private val lock = ReentrantLock() private val lock = ReentrantLock()
private val bridgeNameToBridgeMap = mutableMapOf<String, AMQPBridge>() private val bridgeNameToBridgeMap = mutableMapOf<String, AMQPBridge>()
private var sharedEventLoopGroup: EventLoopGroup? = null private var sharedEventLoopGroup: EventLoopGroup? = null
private val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword) private val keyStore = config.loadSslKeyStore().internal
private val keyStorePrivateKeyPassword: String = config.keyStorePassword private val keyStorePrivateKeyPassword: String = config.keyStorePassword
private val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) private val trustStore = config.loadTrustStore().internal
private var artemis: ArtemisMessagingClient? = null private var artemis: ArtemisMessagingClient? = null
companion object { companion object {

View File

@ -34,6 +34,8 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
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.loadKeyStore import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.nodeapi.internal.requireOnDefaultFileSystem import net.corda.nodeapi.internal.requireOnDefaultFileSystem
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
@ -205,8 +207,8 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
@Throws(IOException::class, KeyStoreException::class) @Throws(IOException::class, KeyStoreException::class)
private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager { private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager {
val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword) val keyStore = config.loadSslKeyStore().internal
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) val trustStore = config.loadTrustStore().internal
val defaultCertPolicies = mapOf( val defaultCertPolicies = mapOf(
PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch, PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch,

View File

@ -23,6 +23,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer
import org.apache.activemq.artemis.core.server.cluster.Transformer import org.apache.activemq.artemis.core.server.cluster.Transformer
import org.apache.activemq.artemis.spi.core.remoting.* import org.apache.activemq.artemis.spi.core.remoting.*
import org.apache.activemq.artemis.utils.ConfigurationHelper import org.apache.activemq.artemis.utils.ConfigurationHelper
import java.security.cert.X509Certificate
import java.time.Duration import java.time.Duration
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
@ -161,7 +162,7 @@ class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
"Peer has wrong subject name in the certificate - expected $expectedLegalNames but got $peerCertificateName. This is either a fatal " + "Peer has wrong subject name in the certificate - expected $expectedLegalNames but got $peerCertificateName. This is either a fatal " +
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!" "misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
} }
X509Utilities.validateCertificateChain(session.localCertificates.last() as java.security.cert.X509Certificate, *session.peerCertificates) X509Utilities.validateCertificateChain(session.localCertificates.last() as X509Certificate, *session.peerCertificates)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
connection.close() connection.close()
log.error(e.message) log.error(e.message)

View File

@ -8,8 +8,6 @@ import net.corda.node.internal.security.RPCSecurityManager
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.crypto.loadKeyStore
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort, maxMessageSize: Int) : SingletonSerializeAsToken(), AutoCloseable { class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort, maxMessageSize: Int) : SingletonSerializeAsToken(), AutoCloseable {
@ -18,7 +16,7 @@ class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: Ne
fun start(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) { fun start(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) {
val locator = artemis.start().sessionFactory.serverLocator val locator = artemis.start().sessionFactory.serverLocator
val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS) val myCert = config.loadSslKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_TLS)
rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, securityManager, CordaX500Name.build(myCert.subjectX500Principal)) rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, securityManager, CordaX500Name.build(myCert.subjectX500Principal))
} }

View File

@ -9,7 +9,7 @@ import java.io.IOException
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.HttpURLConnection.* import java.net.HttpURLConnection.*
import java.net.URL import java.net.URL
import java.security.cert.Certificate import java.security.cert.X509Certificate
import java.util.* import java.util.*
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@ -22,19 +22,19 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr
} }
@Throws(CertificateRequestException::class) @Throws(CertificateRequestException::class)
override fun retrieveCertificates(requestId: String): Array<Certificate>? { override fun retrieveCertificates(requestId: String): List<X509Certificate>? {
// Poll server to download the signed certificate once request has been approved. // Poll server to download the signed certificate once request has been approved.
val conn = URL("$registrationURL/$requestId").openHttpConnection() val conn = URL("$registrationURL/$requestId").openHttpConnection()
conn.requestMethod = "GET" conn.requestMethod = "GET"
return when (conn.responseCode) { return when (conn.responseCode) {
HTTP_OK -> ZipInputStream(conn.inputStream).use { HTTP_OK -> ZipInputStream(conn.inputStream).use {
val certificates = ArrayList<Certificate>() val certificates = ArrayList<X509Certificate>()
val factory = X509CertificateFactory() val factory = X509CertificateFactory()
while (it.nextEntry != null) { while (it.nextEntry != null) {
certificates += factory.generateCertificate(it) certificates += factory.generateCertificate(it)
} }
certificates.toTypedArray() certificates
} }
HTTP_NO_CONTENT -> null HTTP_NO_CONTENT -> null
HTTP_UNAUTHORIZED -> throw CertificateRequestException("Certificate signing request has been rejected: ${conn.errorMessage}") HTTP_UNAUTHORIZED -> throw CertificateRequestException("Certificate signing request has been rejected: ${conn.errorMessage}")

View File

@ -5,7 +5,8 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.crypto.* 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_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS 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.CORDA_ROOT_CA
@ -14,7 +15,6 @@ import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter import java.io.StringWriter
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore import java.security.KeyStore
import java.security.cert.Certificate
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
/** /**
@ -28,10 +28,8 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
} }
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt" private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
private val keystorePassword = config.keyStorePassword
// TODO: Use different password for private key. // TODO: Use different password for private key.
private val privateKeyPassword = config.keyStorePassword private val privateKeyPassword = config.keyStorePassword
private val trustStore: KeyStore
private val rootCert: X509Certificate private val rootCert: X509Certificate
init { init {
@ -39,8 +37,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
"${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " + "${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
"Please contact your CZ operator." "Please contact your CZ operator."
} }
trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) val rootCert = config.loadTrustStore().internal.getCertificate(CORDA_ROOT_CA)
val rootCert = trustStore.getCertificate(CORDA_ROOT_CA)
require(rootCert != null) { require(rootCert != null) {
"${config.trustStoreFile} does not contain a certificate with the key $CORDA_ROOT_CA." + "${config.trustStoreFile} does not contain a certificate with the key $CORDA_ROOT_CA." +
"This file must contain the root CA cert of your compatibility zone. " + "This file must contain the root CA cert of your compatibility zone. " +
@ -62,24 +59,23 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
*/ */
fun buildKeystore() { fun buildKeystore() {
config.certificatesDirectory.createDirectories() config.certificatesDirectory.createDirectories()
val nodeKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) val nodeKeyStore = config.loadNodeKeyStore(createNew = true)
if (nodeKeyStore.containsAlias(CORDA_CLIENT_CA)) { if (CORDA_CLIENT_CA in nodeKeyStore) {
println("Certificate already exists, Corda node will now terminate...") println("Certificate already exists, Corda node will now terminate...")
return return
} }
// Create or load self signed keypair from the key store. // 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. // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
if (!nodeKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) { if (SELF_SIGNED_PRIVATE_KEY !in nodeKeyStore) {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName.x500Principal, keyPair) val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName.x500Principal, keyPair)
// Save to the key store. // Save to the key store.
nodeKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), nodeKeyStore.setPrivateKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
arrayOf(selfSignCert)) nodeKeyStore.save()
nodeKeyStore.save(config.nodeKeystore, keystorePassword)
} }
val keyPair = nodeKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) val keyPair = nodeKeyStore.getCertificateAndKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword).keyPair
val requestId = submitOrResumeCertificateSigningRequest(keyPair) val requestId = submitOrResumeCertificateSigningRequest(keyPair)
val certificates = try { val certificates = try {
@ -92,7 +88,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
throw certificateRequestException throw certificateRequestException
} }
val nodeCaCert = certificates[0] as X509Certificate val nodeCaCert = certificates[0]
val nodeCaSubject = try { val nodeCaSubject = try {
CordaX500Name.build(nodeCaCert.subjectX500Principal) CordaX500Name.build(nodeCaCert.subjectX500Principal)
@ -113,15 +109,16 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
} }
println("Checking root of the certificate path is what we expect.") println("Checking root of the certificate path is what we expect.")
X509Utilities.validateCertificateChain(rootCert, *certificates) X509Utilities.validateCertificateChain(rootCert, *certificates.toTypedArray())
println("Certificate signing request approved, storing private key with the certificate chain.") println("Certificate signing request approved, storing private key with the certificate chain.")
// Save private key and certificate chain to the key store. // Save private key and certificate chain to the key store.
nodeKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) nodeKeyStore.setPrivateKey(CORDA_CLIENT_CA, keyPair.private, certificates, keyPassword = privateKeyPassword)
nodeKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) nodeKeyStore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
nodeKeyStore.save(config.nodeKeystore, keystorePassword) nodeKeyStore.save()
println("Node private key and certificate stored in ${config.nodeKeystore}.") println("Node private key and certificate stored in ${config.nodeKeystore}.")
config.loadSslKeyStore(createNew = true).update {
println("Generating SSL certificate for node messaging service.") println("Generating SSL certificate for node messaging service.")
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val sslCert = X509Utilities.createCertificate( val sslCert = X509Utilities.createCertificate(
@ -130,9 +127,8 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
keyPair, keyPair,
config.myLegalName.x500Principal, config.myLegalName.x500Principal,
sslKeyPair.public) sslKeyPair.public)
val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword) setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKeyPair.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates)) }
sslKeyStore.save(config.sslKeystore, config.keyStorePassword)
println("SSL private key and certificate stored in ${config.sslKeystore}.") println("SSL private key and certificate stored in ${config.sslKeystore}.")
// All done, clean up temp files. // All done, clean up temp files.
@ -145,7 +141,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
* @param requestId Certificate signing request ID. * @param requestId Certificate signing request ID.
* @return Map of certificate chain. * @return Map of certificate chain.
*/ */
private fun pollServerForCertificates(requestId: String): Array<Certificate> { private fun pollServerForCertificates(requestId: String): List<X509Certificate> {
println("Start polling server for certificate signing approval.") println("Start polling server for certificate signing approval.")
// Poll server to download the signed certificate once request has been approved. // Poll server to download the signed certificate once request has been approved.
var certificates = certService.retrieveCertificates(requestId) var certificates = certService.retrieveCertificates(requestId)

View File

@ -3,7 +3,7 @@ package net.corda.node.utilities.registration
import net.corda.core.CordaException import net.corda.core.CordaException
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.cert.Certificate import java.security.cert.X509Certificate
interface NetworkRegistrationService { interface NetworkRegistrationService {
/** Submits a CSR to the signing service and returns an opaque request ID. */ /** Submits a CSR to the signing service and returns an opaque request ID. */
@ -11,7 +11,7 @@ interface NetworkRegistrationService {
/** Poll Certificate Signing Server for the request and returns a chain of certificates if request has been approved, null otherwise. */ /** Poll Certificate Signing Server for the request and returns a chain of certificates if request has been approved, null otherwise. */
@Throws(CertificateRequestException::class) @Throws(CertificateRequestException::class)
fun retrieveCertificates(requestId: String): Array<Certificate>? fun retrieveCertificates(requestId: String): List<X509Certificate>?
} }
@CordaSerializable @CordaSerializable

View File

@ -12,7 +12,8 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectories
import net.corda.core.internal.x500Name import net.corda.core.internal.x500Name
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
@ -27,7 +28,6 @@ import java.security.cert.CertPathValidatorException
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NetworkRegistrationHelperTest { class NetworkRegistrationHelperTest {
private val fs = Jimfs.newFileSystem(unix()) private val fs = Jimfs.newFileSystem(unix())
@ -65,34 +65,31 @@ class NetworkRegistrationHelperTest {
saveTrustStoreWithRootCa(nodeCaCertPath.last()) saveTrustStoreWithRootCa(nodeCaCertPath.last())
createRegistrationHelper(nodeCaCertPath).buildKeystore() createRegistrationHelper(nodeCaCertPath).buildKeystore()
val nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) val nodeKeystore = config.loadNodeKeyStore()
val sslKeystore = loadKeyStore(config.sslKeystore, config.keyStorePassword) val sslKeystore = config.loadSslKeyStore()
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) val trustStore = config.loadTrustStore()
nodeKeystore.run { nodeKeystore.run {
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(contains(X509Utilities.CORDA_ROOT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS))
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactlyElementsOf(nodeCaCertPath)
assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactly(*nodeCaCertPath)
} }
sslKeystore.run { sslKeystore.run {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertFalse(contains(X509Utilities.CORDA_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertFalse(contains(X509Utilities.CORDA_ROOT_CA))
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
val nodeTlsCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) val nodeTlsCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)
assertThat(nodeTlsCertChain).hasSize(4) assertThat(nodeTlsCertChain).hasSize(4)
// The TLS cert has the same subject as the node CA cert // The TLS cert has the same subject as the node CA cert
assertThat(CordaX500Name.build((nodeTlsCertChain[0] as X509Certificate).subjectX500Principal)).isEqualTo(nodeLegalName) assertThat(CordaX500Name.build(nodeTlsCertChain[0].subjectX500Principal)).isEqualTo(nodeLegalName)
assertThat(nodeTlsCertChain.drop(1)).containsExactly(*nodeCaCertPath) assertThat(nodeTlsCertChain.drop(1)).containsExactlyElementsOf(nodeCaCertPath)
} }
trustStore.run { trustStore.run {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertFalse(contains(X509Utilities.CORDA_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(nodeCaCertPath.last()) assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(nodeCaCertPath.last())
} }
} }
@ -139,7 +136,7 @@ class NetworkRegistrationHelperTest {
} }
private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA, private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA,
legalName: CordaX500Name = nodeLegalName): Array<X509Certificate> { legalName: CordaX500Name = nodeLegalName): List<X509Certificate> {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf()) val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
@ -150,10 +147,10 @@ class NetworkRegistrationHelperTest {
legalName.x500Principal, legalName.x500Principal,
keyPair.public, keyPair.public,
nameConstraints = nameConstraints) nameConstraints = nameConstraints)
return arrayOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate)
} }
private fun createRegistrationHelper(response: Array<X509Certificate>): NetworkRegistrationHelper { private fun createRegistrationHelper(response: List<X509Certificate>): NetworkRegistrationHelper {
val certService = rigorousMock<NetworkRegistrationService>().also { val certService = rigorousMock<NetworkRegistrationService>().also {
doReturn(requestId).whenever(it).submitRequest(any()) doReturn(requestId).whenever(it).submitRequest(any())
doReturn(response).whenever(it).retrieveCertificates(eq(requestId)) doReturn(response).whenever(it).retrieveCertificates(eq(requestId))
@ -163,9 +160,8 @@ class NetworkRegistrationHelperTest {
private fun saveTrustStoreWithRootCa(rootCert: X509Certificate) { private fun saveTrustStoreWithRootCa(rootCert: X509Certificate) {
config.certificatesDirectory.createDirectories() config.certificatesDirectory.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { config.loadTrustStore(createNew = true).update {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
it.save(config.trustStoreFile, config.trustStorePassword)
} }
} }
} }

View File

@ -34,16 +34,14 @@ import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.config.toConfig import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.addOrReplaceCertificate
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.nodeapi.internal.crypto.save
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.setGlobalSerialization
import net.corda.testing.driver.* import net.corda.testing.driver.*
import net.corda.testing.node.ClusterSpec import net.corda.testing.node.ClusterSpec
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
@ -51,7 +49,6 @@ import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT
import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT
import net.corda.testing.core.setGlobalSerialization
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import rx.Observable import rx.Observable
@ -239,9 +236,8 @@ class DriverDSLImpl(
)) ))
config.corda.certificatesDirectory.createDirectories() config.corda.certificatesDirectory.createDirectories()
loadOrCreateKeyStore(config.corda.trustStoreFile, config.corda.trustStorePassword).apply { config.corda.loadTrustStore(createNew = true).update {
addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
save(config.corda.trustStoreFile, config.corda.trustStorePassword)
} }
return if (startNodesInProcess) { return if (startNodesInProcess) {