mirror of
https://github.com/corda/corda.git
synced 2025-05-31 22:50:53 +00:00
Replaced KeyStoreWrapper with X509KeyStore, which is still a wrapper but assumes only X509 certs and has better APIs (#2411)
This commit is contained in:
parent
5df50c0e81
commit
61c7de22d6
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()) }
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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,
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}")
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user