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
import net.corda.core.CordaOID
import net.corda.core.utilities.NonEmptySet
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1Integer
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
// 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
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(setOf(null), false, false),
INTERMEDIATE_CA(NonEmptySet.of(null), false, false),
/** 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). */
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_CA(setOf(INTERMEDIATE_CA), false, false),
NODE_CA(NonEmptySet.of(INTERMEDIATE_CA), false, false),
/** 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. */
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_LEGAL_IDENTITY(setOf(LEGAL_IDENTITY), true, false);
CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false);
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.
*
* @throws IllegalArgumentException if the encoded data is not a valid role.
*/
fun getInstance(id: ASN1Integer): CertRole {
if (cachedRoles == null) {
cachedRoles = CertRole.values()
}
val idVal = id.value
require(idVal.compareTo(BigInteger.ZERO) > 0) { "Invalid role ID" }
require(idVal > BigInteger.ZERO) { "Invalid role ID" }
return try {
val ordinal = idVal.intValueExact() - 1
cachedRoles!![ordinal]
values[ordinal]
} catch (ex: ArithmeticException) {
throw IllegalArgumentException("Invalid role ID")
} 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.
* @throws IllegalArgumentException if the extension is present but is invalid.
*/
fun extract(cert: Certificate): CertRole? {
val x509Cert = cert as? X509Certificate
return if (x509Cert != null) {
extract(x509Cert)
} else {
null
}
}
fun extract(cert: Certificate): CertRole? = (cert as? X509Certificate)?.let { extract(it) }
/**
* 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.
*/
fun extract(cert: X509Certificate): CertRole? {
val extensionData: ByteArray? = cert.getExtensionValue(CordaOID.X509_EXTENSION_CORDA_ROLE)
return if (extensionData != null) {
val extensionString = DEROctetString.getInstance(extensionData)
return cert.getExtensionValue(CordaOID.X509_EXTENSION_CORDA_ROLE)?.let {
val extensionString = DEROctetString.getInstance(it)
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.utilities.OpaqueBytes
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.internal.kryoSpecific
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@ -341,9 +345,9 @@ class CompositeKeyTests {
// Store certificate to keystore.
val keystorePath = tempFolder.root.toPath() / "keystore.jks"
val keystore = loadOrCreateKeyStore(keystorePath, "password")
keystore.setCertificateEntry("CompositeKey", compositeKeyCert)
keystore.save(keystorePath, "password")
X509KeyStore.fromFile(keystorePath, "password", createNew = true).update {
setCertificate("CompositeKey", compositeKeyCert)
}
// Load keystore from disk.
val keystore2 = loadKeyStore(keystorePath, "password")
@ -352,7 +356,7 @@ class CompositeKeyTests {
val key = keystore2.getCertificate("CompositeKey").publicKey
// Convert sun public key to Composite key.
val compositeKey2 = Crypto.toSupportedPublicKey(key)
assertTrue { compositeKey2 is CompositeKey }
assertThat(compositeKey2).isInstanceOf(CompositeKey::class.java)
// Run the same composite key test again.
assertTrue { compositeKey2.isFulfilledBy(signatures.byKeys()) }

View File

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

View File

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

View File

@ -1,7 +1,9 @@
package net.corda.nodeapi.internal
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.x500Name
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.*
@ -20,50 +22,47 @@ import javax.security.auth.x500.X500Principal
*/
fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name,
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)
createDevKeyStores(rootCert, intermediateCa, nodeCaCert, nodeCaKeyPair, legalName)
}
/**
* 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(
val nodeKeyStore = loadNodeKeyStore(createNew = true)
nodeKeyStore.update {
setPrivateKey(
X509Utilities.CORDA_CLIENT_CA,
nodeCaKeyPair.private,
keyStorePassword.toCharArray(),
arrayOf(nodeCaCert, intermediateCa.certificate, rootCert))
save(nodeKeystore, keyStorePassword)
listOf(nodeCaCert, intermediateCa.certificate, rootCert))
}
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 {

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.config
import net.corda.core.internal.div
import net.corda.nodeapi.internal.crypto.X509KeyStore
import java.nio.file.Path
interface SSLConfiguration {
@ -11,6 +12,18 @@ interface SSLConfiguration {
// TODO This looks like it should be in NodeSSLConfiguration
val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.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 {

View File

@ -9,7 +9,6 @@ import net.corda.core.internal.read
import net.corda.core.internal.write
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Path
import java.security.*
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,
* but for SSL purposes this is recommended.
*/
fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) = keyStoreFilePath.write { store(it, storePassword) }
fun KeyStore.store(out: OutputStream, password: String) = store(out, password.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
fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) {
keyStoreFilePath.write {
store(it, storePassword.toCharArray())
}
}
/**
* 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 {
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.reader
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.millis
import org.bouncycastle.asn1.*
@ -95,6 +93,7 @@ object X509Utilities {
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)
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
@ -287,10 +286,12 @@ class X509CertificateFactory {
return delegate.generateCertificate(input) as X509Certificate
}
// TODO X509Certificate
fun generateCertPath(certificates: List<Certificate>): CertPath {
return delegate.generateCertPath(certificates)
}
// TODO X509Certificate
fun generateCertPath(vararg certificates: Certificate): CertPath {
return delegate.generateCertPath(certificates.asList())
}

View File

@ -222,7 +222,8 @@ class X509UtilitiesTest {
val clientSocketFactory = context.socketFactory
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.needClientAuth = true
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
@ -230,7 +231,8 @@ class X509UtilitiesTest {
serverSocket.useClientMode = false
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.
clientSocket.sslParameters = clientParams
clientSocket.useClientMode = true
@ -267,7 +269,7 @@ class X509UtilitiesTest {
val peerChain = clientSocket.session.peerCertificates
val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
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)
output.writeUTF("Hello World")
var timeout = 0

View File

@ -5,7 +5,8 @@ import net.corda.core.internal.div
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.config.configureDevKeyAndTrustStores
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.driver.driver
import org.assertj.core.api.Assertions.assertThatThrownBy
@ -45,15 +46,14 @@ class NodeKeystoreCheckTest {
node.stop()
// Fiddle with node keystore.
val keystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword)
// Self signed root
val badRootKeyPair = Crypto.generateKeyPair()
val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair)
val nodeCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword)
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))
keystore.save(config.nodeKeystore, config.keyStorePassword)
config.loadNodeKeyStore().update {
// Self signed root
val badRootKeyPair = Crypto.generateKeyPair()
val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair)
val nodeCA = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public)
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, listOf(badNodeCACert, badRoot))
}
assertThatThrownBy {
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.ArtemisMessagingServer
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
@ -229,16 +228,14 @@ class AMQPBridgeTest {
}
serverConfig.configureWithDevSSLCertificate()
val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword)
val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
val amqpServer = AMQPServer("0.0.0.0",
return AMQPServer("0.0.0.0",
amqpPort,
ArtemisMessagingComponent.PEER_USER,
ArtemisMessagingComponent.PEER_USER,
serverKeystore,
serverConfig.loadSslKeyStore().internal,
serverConfig.keyStorePassword,
serverTruststore,
trace = true)
return amqpServer
serverConfig.loadTrustStore().internal,
trace = true
)
}
}

View File

@ -21,7 +21,6 @@ import net.corda.node.services.messaging.ArtemisMessagingClient
import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
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.internal.rigorousMock
import org.apache.activemq.artemis.api.core.RoutingType
@ -253,8 +252,8 @@ class ProtonWrapperTests {
}
clientConfig.configureWithDevSSLCertificate()
val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword)
val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword)
val clientTruststore = clientConfig.loadTrustStore().internal
val clientKeystore = clientConfig.loadSslKeyStore().internal
return AMQPClient(
listOf(NetworkHostAndPort("localhost", serverPort),
NetworkHostAndPort("localhost", serverPort2),
@ -276,8 +275,8 @@ class ProtonWrapperTests {
}
clientConfig.configureWithDevSSLCertificate()
val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword)
val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword)
val clientTruststore = clientConfig.loadTrustStore().internal
val clientKeystore = clientConfig.loadSslKeyStore().internal
return AMQPClient(
listOf(NetworkHostAndPort("localhost", serverPort)),
setOf(ALICE_NAME),
@ -297,8 +296,8 @@ class ProtonWrapperTests {
}
serverConfig.configureWithDevSSLCertificate()
val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword)
val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
val serverTruststore = serverConfig.loadTrustStore().internal
val serverKeystore = serverConfig.loadSslKeyStore().internal
return AMQPServer(
"0.0.0.0",
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_ROOT_CA
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.core.ActiveMQClusterSecurityException
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
@ -115,22 +116,19 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
CordaX500Name("MiniCorp", "London", "GB").x500Principal,
tlsKeyPair.public)
val keyPass = keyStorePassword.toCharArray()
val clientCAKeystore = loadOrCreateKeyStore(nodeKeystore, keyStorePassword)
clientCAKeystore.addOrReplaceKey(
X509Utilities.CORDA_CLIENT_CA,
clientKeyPair.private,
keyPass,
arrayOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
clientCAKeystore.save(nodeKeystore, keyStorePassword)
loadNodeKeyStore(createNew = true).update {
setPrivateKey(
X509Utilities.CORDA_CLIENT_CA,
clientKeyPair.private,
listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
}
val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword)
tlsKeystore.addOrReplaceKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
keyPass,
arrayOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
tlsKeystore.save(sslKeystore, keyStorePassword)
loadSslKeyStore(createNew = true).update {
setPrivateKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
}
}
}

View File

@ -36,7 +36,10 @@ import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
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.ScheduledActivityObserver
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.nodeapi.internal.DevIdentityGenerator
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.X509Utilities
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.nodeapi.internal.storeLegalIdentity
import org.apache.activemq.artemis.utils.ReusableLatch
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
import org.slf4j.Logger
@ -140,7 +142,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
private val _nodeReadyFuture = openFuture<Unit>()
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
* or has loaded network map data from local database */
@ -568,9 +570,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
private fun validateKeystore() {
val containCorrectKeys = try {
// This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword)
val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword)
sslKeystore.containsAlias(X509Utilities.CORDA_CLIENT_TLS) && identitiesKeystore.containsAlias(X509Utilities.CORDA_CLIENT_CA)
val sslKeystore = configuration.loadSslKeyStore()
val identitiesKeystore = configuration.loadNodeKeyStore()
X509Utilities.CORDA_CLIENT_TLS in sslKeystore && X509Utilities.CORDA_CLIENT_CA in identitiesKeystore
} catch (e: KeyStoreException) {
log.warn("Certificate key store found but key store password does not match configuration.")
false
@ -585,15 +587,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
// Check all cert path chain to the trusted root
val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword)
val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword)
val trustStore = loadKeyStore(configuration.trustStoreFile, configuration.trustStorePassword)
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)
val sslCertChainRoot = configuration.loadSslKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last()
val nodeCaCertChainRoot = configuration.loadNodeKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last()
val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
require(sslRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
require(clientCARoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
require(sslCertChainRoot == trustRoot) { "TLS 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.
@ -684,12 +683,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
private fun makeIdentityService(identityCert: X509Certificate): PersistentIdentityService {
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
val clientCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val caCertificates = arrayOf(identityCert, clientCa.certificate)
return PersistentIdentityService(trustRoot, *caCertificates)
val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA)
return PersistentIdentityService(trustRoot, identityCert, nodeCa)
}
protected abstract fun makeTransactionVerifierService(): TransactionVerifierService
@ -713,7 +709,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
protected abstract fun startMessagingService(rpcOps: RPCOps)
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) {
// 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?
val privateKeyAlias = "$id-private-key"
if (!keyStore.containsAlias(privateKeyAlias)) {
if (privateKeyAlias !in keyStore) {
singleName ?: throw IllegalArgumentException(
"Unable to find in the key store the identity of the distributed notary ($id) the node is part of")
// TODO: Remove use of [IdentityGenerator.generateToDisk].
"Unable to find in the key store the identity of the distributed notary the node is part of")
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)
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val compositeKeyAlias = "$id-composite-key"
val certificates = if (keyStore.containsAlias(compositeKeyAlias)) {
val certificates = if (compositeKeyAlias in keyStore) {
// Use composite key instead if it exists
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
@ -747,12 +744,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} else {
keyStore.getCertificateChain(privateKeyAlias).let {
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(nodeCert.subjectX500Principal)
val subject = CordaX500Name.build(certificates[0].subjectX500Principal)
// 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
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.nodeapi.internal.config.SSLConfiguration
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 java.nio.file.Path
@ -52,22 +54,21 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
}
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
createDevKeyStores(myLegalName)
val (nodeKeyStore) = createDevKeyStores(myLegalName)
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
if (distributedServiceKeystore.exists()) {
val serviceKeystore = loadKeyStore(distributedServiceKeystore, "cordacadevpass")
val cordaNodeKeystore = loadKeyStore(nodeKeystore, keyStorePassword)
serviceKeystore.aliases().iterator().forEach {
if (serviceKeystore.isKeyEntry(it)) {
cordaNodeKeystore.setKeyEntry(it, serviceKeystore.getKey(it, "cordacadevkeypass".toCharArray()), keyStorePassword.toCharArray(), serviceKeystore.getCertificateChain(it))
} else {
cordaNodeKeystore.setCertificateEntry(it, serviceKeystore.getCertificate(it))
val serviceKeystore = X509KeyStore.fromFile(distributedServiceKeystore, "cordacadevpass")
nodeKeyStore.update {
serviceKeystore.aliases().forEach {
if (serviceKeystore.internal.isKeyEntry(it)) {
setPrivateKey(it, serviceKeystore.getPrivateKey(it, "cordacadevkeypass"), serviceKeystore.getCertificateChain(it))
} else {
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.PEER_USER
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.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
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 bridgeNameToBridgeMap = mutableMapOf<String, AMQPBridge>()
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 trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
private val trustStore = config.loadTrustStore().internal
private var artemis: ArtemisMessagingClient? = null
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.PEER_USER
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.requireOnDefaultFileSystem
import org.apache.activemq.artemis.api.core.SimpleString
@ -205,8 +207,8 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
@Throws(IOException::class, KeyStoreException::class)
private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager {
val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
val keyStore = config.loadSslKeyStore().internal
val trustStore = config.loadTrustStore().internal
val defaultCertPolicies = mapOf(
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.spi.core.remoting.*
import org.apache.activemq.artemis.utils.ConfigurationHelper
import java.security.cert.X509Certificate
import java.time.Duration
import java.util.concurrent.Executor
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 " +
"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) {
connection.close()
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.config.SSLConfiguration
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
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) {
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))
}

View File

@ -9,7 +9,7 @@ import java.io.IOException
import java.net.HttpURLConnection
import java.net.HttpURLConnection.*
import java.net.URL
import java.security.cert.Certificate
import java.security.cert.X509Certificate
import java.util.*
import java.util.zip.ZipInputStream
@ -22,19 +22,19 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr
}
@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.
val conn = URL("$registrationURL/$requestId").openHttpConnection()
conn.requestMethod = "GET"
return when (conn.responseCode) {
HTTP_OK -> ZipInputStream(conn.inputStream).use {
val certificates = ArrayList<Certificate>()
val certificates = ArrayList<X509Certificate>()
val factory = X509CertificateFactory()
while (it.nextEntry != null) {
certificates += factory.generateCertificate(it)
}
certificates.toTypedArray()
certificates
}
HTTP_NO_CONTENT -> null
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.utilities.seconds
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_TLS
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.security.KeyPair
import java.security.KeyStore
import java.security.cert.Certificate
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 keystorePassword = config.keyStorePassword
// TODO: Use different password for private key.
private val privateKeyPassword = config.keyStorePassword
private val trustStore: KeyStore
private val rootCert: X509Certificate
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. " +
"Please contact your CZ operator."
}
trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
val rootCert = trustStore.getCertificate(CORDA_ROOT_CA)
val rootCert = config.loadTrustStore().internal.getCertificate(CORDA_ROOT_CA)
require(rootCert != null) {
"${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. " +
@ -62,24 +59,23 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
*/
fun buildKeystore() {
config.certificatesDirectory.createDirectories()
val nodeKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword)
if (nodeKeyStore.containsAlias(CORDA_CLIENT_CA)) {
val nodeKeyStore = config.loadNodeKeyStore(createNew = true)
if (CORDA_CLIENT_CA in nodeKeyStore) {
println("Certificate already exists, Corda node will now terminate...")
return
}
// 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.
if (!nodeKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) {
if (SELF_SIGNED_PRIVATE_KEY !in nodeKeyStore) {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName.x500Principal, keyPair)
// Save to the key store.
nodeKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(),
arrayOf(selfSignCert))
nodeKeyStore.save(config.nodeKeystore, keystorePassword)
nodeKeyStore.setPrivateKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
nodeKeyStore.save()
}
val keyPair = nodeKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
val keyPair = nodeKeyStore.getCertificateAndKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword).keyPair
val requestId = submitOrResumeCertificateSigningRequest(keyPair)
val certificates = try {
@ -92,7 +88,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
throw certificateRequestException
}
val nodeCaCert = certificates[0] as X509Certificate
val nodeCaCert = certificates[0]
val nodeCaSubject = try {
CordaX500Name.build(nodeCaCert.subjectX500Principal)
@ -113,26 +109,26 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
}
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.")
// Save private key and certificate chain to the key store.
nodeKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
nodeKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
nodeKeyStore.save(config.nodeKeystore, keystorePassword)
nodeKeyStore.setPrivateKey(CORDA_CLIENT_CA, keyPair.private, certificates, keyPassword = privateKeyPassword)
nodeKeyStore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
nodeKeyStore.save()
println("Node private key and certificate stored in ${config.nodeKeystore}.")
println("Generating SSL certificate for node messaging service.")
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val sslCert = X509Utilities.createCertificate(
CertificateType.TLS,
nodeCaCert,
keyPair,
config.myLegalName.x500Principal,
sslKeyPair.public)
val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword)
sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKeyPair.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates))
sslKeyStore.save(config.sslKeystore, config.keyStorePassword)
config.loadSslKeyStore(createNew = true).update {
println("Generating SSL certificate for node messaging service.")
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val sslCert = X509Utilities.createCertificate(
CertificateType.TLS,
nodeCaCert,
keyPair,
config.myLegalName.x500Principal,
sslKeyPair.public)
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
}
println("SSL private key and certificate stored in ${config.sslKeystore}.")
// All done, clean up temp files.
@ -145,7 +141,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
* @param requestId Certificate signing request ID.
* @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.")
// Poll server to download the signed certificate once request has been approved.
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.serialization.CordaSerializable
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.cert.Certificate
import java.security.cert.X509Certificate
interface NetworkRegistrationService {
/** 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. */
@Throws(CertificateRequestException::class)
fun retrieveCertificates(requestId: String): Array<Certificate>?
fun retrieveCertificates(requestId: String): List<X509Certificate>?
}
@CordaSerializable

View File

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

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.toConfig
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.NodeInfoFilesCopier
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.BOB_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.node.ClusterSpec
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.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT
import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT
import net.corda.testing.core.setGlobalSerialization
import okhttp3.OkHttpClient
import okhttp3.Request
import rx.Observable
@ -239,9 +236,8 @@ class DriverDSLImpl(
))
config.corda.certificatesDirectory.createDirectories()
loadOrCreateKeyStore(config.corda.trustStoreFile, config.corda.trustStorePassword).apply {
addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
save(config.corda.trustStoreFile, config.corda.trustStorePassword)
config.corda.loadTrustStore(createNew = true).update {
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
}
return if (startNodesInProcess) {