Merge commit '246de55433f747707b2d0dd6299437c664ea933d' into mike-enterprise-remerge

Includes API updates to the doorman code.
This commit is contained in:
Mike Hearn 2017-06-12 16:25:30 +02:00
commit 782d4bd731
69 changed files with 1187 additions and 655 deletions

8
.gitignore vendored
View File

@ -32,7 +32,7 @@ lib/dokka.jar
.idea/libraries
.idea/shelf
.idea/dataSources
/gradle-plugins/.idea
/gradle-plugins/.idea/
# Include the -parameters compiler option by default in IntelliJ required for serialization.
!.idea/compiler.xml
@ -84,8 +84,10 @@ crashlytics-build.properties
docs/virtualenv/
# bft-smart
node/bft-smart-config/currentView
node/config/currentView
config/currentView
# vim
*.swp
# Files you may find useful to have in your working directory.
PLAN

6
.idea/compiler.xml generated
View File

@ -61,10 +61,10 @@
<module name="node_integrationTest" target="1.8" />
<module name="node_main" target="1.8" />
<module name="node_test" target="1.8" />
<module name="notary-demo_main" target="1.8" />
<module name="notary-demo_test" target="1.8" />
<module name="quasar-hook_main" target="1.8" />
<module name="quasar-hook_test" target="1.8" />
<module name="raft-notary-demo_main" target="1.8" />
<module name="raft-notary-demo_test" target="1.8" />
<module name="rpc_integrationTest" target="1.8" />
<module name="rpc_main" target="1.8" />
<module name="rpc_smokeTest" target="1.8" />
@ -98,4 +98,4 @@
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component>
</project>
</project>

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=0.12.1
gradlePluginsVersion=0.12.2
kotlinVersion=1.1.2
guavaVersion=21.0
bouncycastleVersion=1.56

View File

@ -25,7 +25,8 @@ public class CordformNode {
public List<String> advertisedServices = emptyList();
/**
* If running a distributed notary, a list of node addresses for joining the Raft cluster
* If running a Raft notary cluster, the address of at least one node in the cluster, or leave blank to start a new cluster.
* If running a BFT notary cluster, the addresses of all nodes in the cluster.
*/
public List<String> notaryClusterAddresses = emptyList();
/**
@ -82,11 +83,18 @@ public class CordformNode {
}
/**
* Set the port which to bind the Copycat (Raft) node to
* Set the port which to bind the Copycat (Raft) node to.
*
* @param notaryPort The Raft port.
*/
public void notaryNodePort(Integer notaryPort) {
config = config.withValue("notaryNodeAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + notaryPort));
}
/**
* @param id The (0-based) BFT replica ID.
*/
public void bftReplicaId(Integer id) {
config = config.withValue("bftReplicaId", ConfigValueFactory.fromAnyRef(id));
}
}

View File

@ -110,8 +110,9 @@ infix fun <T> ListenableFuture<T>.failure(body: (Throwable) -> Unit): Listenable
infix fun <F, T> ListenableFuture<F>.map(mapper: (F) -> T): ListenableFuture<T> = Futures.transform(this, { (mapper as (F?) -> T)(it) })
infix fun <F, T> ListenableFuture<F>.flatMap(mapper: (F) -> ListenableFuture<T>): ListenableFuture<T> = Futures.transformAsync(this) { mapper(it!!) }
inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R) = run {
val iterator = iterator()
inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R) = mapToArray(transform, iterator(), size)
inline fun <reified R> IntProgression.mapToArray(transform: (Int) -> R) = mapToArray(transform, iterator(), 1 + (last - first) / step)
inline fun <T, reified R> mapToArray(transform: (T) -> R, iterator: Iterator<T>, size: Int) = run {
var expected = 0
Array(size) {
expected++ == it || throw UnsupportedOperationException("Array constructor is non-sequential!")

View File

@ -14,7 +14,10 @@ import org.bouncycastle.asn1.bc.BCObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.asn1.x509.BasicConstraints
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
@ -558,26 +561,26 @@ object Crypto {
/**
* Use bouncy castle utilities to sign completed X509 certificate with CA cert private key.
*/
fun createCertificate(issuer: X500Name, issuerKeyPair: KeyPair,
fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair,
subject: X500Name, subjectPublicKey: PublicKey,
keyUsage: KeyUsage, purposes: List<KeyPurposeId>,
validityWindow: Pair<Date, Date>,
pathLength: Int? = null, subjectAlternativeName: List<GeneralName>? = null): X509Certificate {
nameConstraints: NameConstraints? = null): X509Certificate {
val signatureScheme = findSignatureScheme(issuerKeyPair.private)
val provider = providerMap[signatureScheme.providerName]
val serial = BigInteger.valueOf(random63BitValue())
val keyPurposes = DERSequence(ASN1EncodableVector().apply { purposes.forEach { add(it) } })
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectPublicKey.encoded)))
.addExtension(Extension.basicConstraints, pathLength != null, if (pathLength == null) BasicConstraints(false) else BasicConstraints(pathLength))
.addExtension(Extension.keyUsage, false, keyUsage)
.addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA))
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
if (subjectAlternativeName != null && subjectAlternativeName.isNotEmpty()) {
builder.addExtension(Extension.subjectAlternativeName, false, DERSequence(subjectAlternativeName.toTypedArray()))
if (nameConstraints != null) {
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
}
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
return JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer)).apply {
checkValidity(Date())

View File

@ -5,23 +5,19 @@ import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.KeyPurposeId
import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.IPAddress
import org.bouncycastle.util.io.pem.PemReader
import java.io.FileReader
import java.io.FileWriter
import java.io.InputStream
import java.net.InetAddress
import java.nio.file.Path
import java.security.InvalidAlgorithmParameterException
import java.security.KeyPair
import java.security.KeyStore
import java.security.PublicKey
import java.security.cert.*
import java.security.cert.Certificate
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit
@ -31,20 +27,12 @@ object X509Utilities {
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
// Aliases for private keys and certificates.
val CORDA_ROOT_CA_PRIVATE_KEY = "cordarootcaprivatekey"
val CORDA_ROOT_CA = "cordarootca"
val CORDA_INTERMEDIATE_CA_PRIVATE_KEY = "cordaintermediatecaprivatekey"
val CORDA_INTERMEDIATE_CA = "cordaintermediateca"
val CORDA_CLIENT_CA_PRIVATE_KEY = "cordaclientcaprivatekey"
val CORDA_CLIENT_TLS = "cordaclienttls"
val CORDA_CLIENT_CA = "cordaclientca"
private val CA_KEY_USAGE = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign)
private val CLIENT_KEY_USAGE = KeyUsage(KeyUsage.digitalSignature)
private val CA_KEY_PURPOSES = listOf(KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage)
private val CLIENT_KEY_PURPOSES = listOf(KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth)
private val DEFAULT_VALIDITY_WINDOW = Pair(Duration.ofMillis(0), Duration.ofDays(365 * 10))
/**
* Helper function to return the latest out of an instant and an optional date.
*/
@ -110,101 +98,59 @@ object X509Utilities {
}
/*
* Create a de novo root self-signed X509 v3 CA cert and [KeyPair].
* @param subject the cert Subject will be populated with the domain string.
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
* @return A data class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates.
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates.
* Create a de novo root self-signed X509 v3 CA cert.
*/
@JvmStatic
fun createSelfSignedCACert(subject: X500Name,
keyPair: KeyPair,
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
val cert = Crypto.createCertificate(subject, keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 2)
return CertificateAndKeyPair(cert, keyPair)
val cert = Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
return cert
}
@JvmStatic
fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair
= createSelfSignedCACert(subject, generateKeyPair(signatureScheme), validityWindow)
/**
* Create a de novo root intermediate X509 v3 CA cert and KeyPair.
* Create a X509 v3 cert.
* @param issuerCertificate The Public certificate of the root CA above this used to sign it.
* @param issuerKeyPair The KeyPair of the root CA above this used to sign it.
* @param subject subject of the generated certificate.
* @param ca The Public certificate and KeyPair of the root CA certificate above this used to sign it.
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
* @param subjectPublicKey subject 's public key.
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
* @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates.
* Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates.
*/
@JvmStatic
fun createIntermediateCert(subject: X500Name,
ca: CertificateAndKeyPair,
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
val keyPair = generateKeyPair(signatureScheme)
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate)
val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 1)
return CertificateAndKeyPair(cert, keyPair)
}
/**
* Create an X509v3 certificate suitable for use in TLS roles.
* @param subject The contents to put in the subject field of the certificate.
* @param publicKey The PublicKey to be wrapped in the certificate.
* @param ca The Public certificate and KeyPair of the parent CA that will sign this certificate.
* @param subjectAlternativeNameDomains A set of alternate DNS names to be supported by the certificate during validation of the TLS handshakes.
* @param subjectAlternativeNameIps A set of alternate IP addresses to be supported by the certificate during validation of the TLS handshakes.
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
* @return The generated X509Certificate suitable for use as a Server/Client certificate in TLS.
* This certificate is not marked as a CA cert to be similar in nature to commercial certificates.
*/
@JvmStatic
fun createTlsServerCert(subject: X500Name, publicKey: PublicKey,
ca: CertificateAndKeyPair,
subjectAlternativeNameDomains: List<String>,
subjectAlternativeNameIps: List<String>,
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate)
val dnsNames = subjectAlternativeNameDomains.map { GeneralName(GeneralName.dNSName, it) }
val ipAddresses = subjectAlternativeNameIps.filter {
IPAddress.isValidIPv6WithNetmask(it) || IPAddress.isValidIPv6(it) || IPAddress.isValidIPv4WithNetmask(it) || IPAddress.isValidIPv4(it)
}.map { GeneralName(GeneralName.iPAddress, it) }
return Crypto.createCertificate(issuer, ca.keyPair, subject, publicKey, CLIENT_KEY_USAGE, CLIENT_KEY_PURPOSES, window, subjectAlternativeName = dnsNames + ipAddresses)
fun createCertificate(certificateType: CertificateType,
issuerCertificate: X509Certificate, issuerKeyPair: KeyPair,
subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
nameConstraints: NameConstraints? = null): X509Certificate {
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
val cert = Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
return cert
}
/**
* Build a certificate path from a trusted root certificate to a target certificate. This will always return a path
* directly from the root to the target, with no intermediate certificates (presuming that path is valid).
* directly from the target to the root.
*
* @param rootCertAndKey trusted root certificate that will be the start of the path.
* @param targetCertAndKey certificate the path ends at.
* @param trustedRoot trusted root certificate that will be the start of the path.
* @param certificates certificates in the path.
* @param revocationEnabled whether revocation of certificates in the path should be checked.
*/
fun createCertificatePath(rootCertAndKey: CertificateAndKeyPair,
targetCertAndKey: X509Certificate,
revocationEnabled: Boolean): CertPathBuilderResult {
val intermediateCertificates = setOf(targetCertAndKey)
val certStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(intermediateCertificates))
val certPathFactory = CertPathBuilder.getInstance("PKIX")
val trustAnchor = TrustAnchor(rootCertAndKey.certificate, null)
val certPathParameters = try {
PKIXBuilderParameters(setOf(trustAnchor), X509CertSelector().apply {
certificate = targetCertAndKey
})
} catch (ex: InvalidAlgorithmParameterException) {
throw RuntimeException(ex)
}.apply {
addCertStore(certStore)
isRevocationEnabled = revocationEnabled
}
return certPathFactory.build(certPathParameters)
fun createCertificatePath(trustedRoot: X509Certificate, vararg certificates: X509Certificate, revocationEnabled: Boolean): CertPath {
val certFactory = CertificateFactory.getInstance("X509")
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
params.isRevocationEnabled = revocationEnabled
return certFactory.generateCertPath(certificates.toList())
}
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
val certFactory = CertificateFactory.getInstance("X509")
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(certificates.toList())
val pathValidator = CertPathValidator.getInstance("PKIX")
pathValidator.validate(certPath, params)
}
/**
@ -237,7 +183,8 @@ object X509Utilities {
/**
* An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine.
* @param keyStoreFilePath KeyStore path to save output to.
* @param sslKeyStorePath KeyStore path to save ssl key and cert to.
* @param clientCAKeystorePath KeyStore path to save client CA key and cert to.
* @param storePassword access password for KeyStore.
* @param keyPassword PrivateKey access password for the generated keys.
* It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same.
@ -245,57 +192,66 @@ object X509Utilities {
* @param caKeyPassword password to unlock private keys in the CA KeyStore.
* @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications.
*/
fun createKeystoreForSSL(keyStoreFilePath: Path,
storePassword: String,
keyPassword: String,
caKeyStore: KeyStore,
caKeyPassword: String,
commonName: X500Name,
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME): KeyStore {
fun createKeystoreForCordaNode(sslKeyStorePath: Path,
clientCAKeystorePath: Path,
storePassword: String,
keyPassword: String,
caKeyStore: KeyStore,
caKeyPassword: String,
legalName: X500Name,
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) {
val rootCA = caKeyStore.getCertificateAndKeyPair(CORDA_ROOT_CA_PRIVATE_KEY, caKeyPassword)
val intermediateCA = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, caKeyPassword)
val rootCACert = caKeyStore.getX509Certificate(CORDA_ROOT_CA)
val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA, caKeyPassword)
val serverKey = generateKeyPair(signatureScheme)
val host = InetAddress.getLocalHost()
val serverCert = createTlsServerCert(commonName, serverKey.public, intermediateCA, listOf(host.hostName), listOf(host.hostAddress))
val clientKey = generateKeyPair(signatureScheme)
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf())
val clientCACert = createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, legalName, clientKey.public, nameConstraints = nameConstraints)
val tlsKey = generateKeyPair(signatureScheme)
val clientTLSCert = createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public)
val keyPass = keyPassword.toCharArray()
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
keyStore.addOrReplaceKey(
CORDA_CLIENT_CA_PRIVATE_KEY,
serverKey.private,
val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(clientCAKeystorePath, storePassword)
clientCAKeystore.addOrReplaceKey(
CORDA_CLIENT_CA,
clientKey.private,
keyPass,
arrayOf(serverCert, intermediateCA.certificate, rootCA.certificate))
keyStore.addOrReplaceCertificate(CORDA_CLIENT_CA, serverCert)
keyStore.save(keyStoreFilePath, storePassword)
return keyStore
arrayOf(clientCACert, intermediateCACert, rootCACert))
clientCAKeystore.save(clientCAKeystorePath, storePassword)
val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeyStorePath, storePassword)
tlsKeystore.addOrReplaceKey(
CORDA_CLIENT_TLS,
tlsKey.private,
keyPass,
arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))
tlsKeystore.save(sslKeyStorePath, storePassword)
}
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) = Crypto.createCertificateSigningRequest(subject, keyPair, signatureScheme)
}
/**
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present, this throws an
* exception.
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
*/
@Throws(IllegalArgumentException::class)
fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
/**
* Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
* adds one.
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
*/
@Throws(IllegalArgumentException::class)
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
/**
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
*
* @param mutator a function to generate the new value from the previous one.
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
*/
@Throws(IllegalArgumentException::class)
private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
val builder = X500NameBuilder(BCStyle.INSTANCE)
var matched = false
@ -319,6 +275,7 @@ private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500N
val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
val X509Certificate.subject: X500Name get() = X509CertificateHolder(encoded).subject
class CertificateStream(val input: InputStream) {
private val certificateFactory = CertificateFactory.getInstance("X.509")
@ -327,3 +284,11 @@ class CertificateStream(val input: InputStream) {
}
data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair)
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
CLIENT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
TLS(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.keyAgreement), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false),
IDENTITY(KeyUsage(KeyUsage.digitalSignature), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false)
}

View File

@ -0,0 +1,26 @@
package net.corda.core.internal
interface ShutdownHook {
/**
* Safe to call from the block passed into [addShutdownHook].
*/
fun cancel()
}
/**
* The given block will run on most kinds of termination including SIGTERM, but not on SIGKILL.
* @return An object via which you can cancel the hook.
*/
fun addShutdownHook(block: () -> Unit): ShutdownHook {
val hook = Thread { block() }
val runtime = Runtime.getRuntime()
runtime.addShutdownHook(hook)
return object : ShutdownHook {
override fun cancel() {
// Allow the block to call cancel without causing IllegalStateException in the shutdown case:
if (Thread.currentThread() != hook) {
runtime.removeShutdownHook(hook)
}
}
}
}

View File

@ -0,0 +1,124 @@
package net.corda.core.crypto
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Test
import java.security.KeyStore
import java.security.cert.CertPathValidator
import java.security.cert.CertPathValidatorException
import java.security.cert.CertificateFactory
import java.security.cert.PKIXParameters
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class X509NameConstraintsTest {
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Root CA"), rootKeys)
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, X509Utilities.getDevX509Name("Corda Intermediate CA"), intermediateCAKeyPair.public)
val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, X509Utilities.getDevX509Name("Corda Client CA"), clientCAKeyPair.public, nameConstraints = nameConstraints)
val keyPass = "password"
val trustStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE)
trustStore.load(null, keyPass.toCharArray())
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert)
val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public)
val keyStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE)
keyStore.load(null, keyPass.toCharArray())
keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), arrayOf(tlsCert, clientCACert, intermediateCACert, rootCACert))
return Pair(keyStore, trustStore)
}
@Test
fun `illegal common name`() {
val acceptableNames = listOf("CN=Bank A TLS, O=Bank A", "CN=Bank A")
.map { GeneralSubtree(GeneralName(X500Name(it))) }.toTypedArray()
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
val pathValidator = CertPathValidator.getInstance("PKIX")
val certFactory = CertificateFactory.getInstance("X509")
assertFailsWith(CertPathValidatorException::class) {
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints)
val params = PKIXParameters(trustStore)
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
pathValidator.validate(certPath, params)
}
assertTrue {
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A TLS, O=Bank A"), nameConstraints)
val params = PKIXParameters(trustStore)
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
pathValidator.validate(certPath, params)
true
}
assertTrue {
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A"), nameConstraints)
val params = PKIXParameters(trustStore)
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
pathValidator.validate(certPath, params)
true
}
}
@Test
fun `x500 name with correct cn and extra attribute`() {
val acceptableNames = listOf("CN=Bank A TLS, UID=", "O=Bank A")
.map { GeneralSubtree(GeneralName(X500Name(it))) }.toTypedArray()
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
val certFactory = CertificateFactory.getInstance("X509")
Crypto.ECDSA_SECP256R1_SHA256
val pathValidator = CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME)
assertFailsWith(CertPathValidatorException::class) {
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A"), nameConstraints)
val params = PKIXParameters(trustStore)
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
pathValidator.validate(certPath, params)
}
assertFailsWith(CertPathValidatorException::class) {
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A, UID=12345"), nameConstraints)
val params = PKIXParameters(trustStore)
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
pathValidator.validate(certPath, params)
}
assertTrue {
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A TLS, UID=, E=me@email.com, C=UK"), nameConstraints)
val params = PKIXParameters(trustStore)
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
pathValidator.validate(certPath, params)
true
}
assertTrue {
val (keystore, trustStore) = makeKeyStores(X500Name("O=Bank A, UID=, E=me@email.com, C=UK"), nameConstraints)
val params = PKIXParameters(trustStore)
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
pathValidator.validate(certPath, params)
true
}
}
}

View File

@ -1,10 +1,13 @@
package net.corda.core.crypto
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate
import net.corda.core.div
import net.corda.testing.MEGA_CORP
import net.corda.testing.getTestX509Name
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@ -33,61 +36,53 @@ class X509UtilitiesTest {
@Test
fun `create valid self-signed CA certificate`() {
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test Cert"))
assertTrue { caCertAndKey.certificate.subjectDN.name.contains("CN=Test Cert") } // using our subject common name
assertEquals(caCertAndKey.certificate.issuerDN, caCertAndKey.certificate.subjectDN) //self-signed
caCertAndKey.certificate.checkValidity(Date()) // throws on verification problems
caCertAndKey.certificate.verify(caCertAndKey.keyPair.public) // throws on verification problems
assertTrue { caCertAndKey.certificate.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
assertTrue { caCertAndKey.certificate.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
assertTrue { caCert.subjectDN.name.contains("CN=Test Cert") } // using our subject common name
assertEquals(caCert.issuerDN, caCert.subjectDN) //self-signed
caCert.checkValidity(Date()) // throws on verification problems
caCert.verify(caKey.public) // throws on verification problems
assertTrue { caCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
assertTrue { caCert.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate
}
@Test
fun `load and save a PEM file certificate`() {
val tmpCertificateFile = tempFile("cacert.pem")
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test Cert"))
X509Utilities.saveCertificateAsPEMFile(caCertAndKey.certificate, tmpCertificateFile)
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
assertEquals(caCertAndKey.certificate, readCertificate)
assertEquals(caCert, readCertificate)
}
@Test
fun `create valid server certificate chain`() {
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test CA Cert"))
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = createSelfSignedCACertificate(getTestX509Name("Test CA Cert"), caKey)
val subjectDN = getTestX509Name("Server Cert")
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val serverCert = X509Utilities.createTlsServerCert(subjectDN, keyPair.public, caCertAndKey, listOf("alias name"), listOf("10.0.0.54"))
val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subjectDN, keyPair.public)
assertTrue { serverCert.subjectDN.name.contains("CN=Server Cert") } // using our subject common name
assertEquals(caCertAndKey.certificate.issuerDN, serverCert.issuerDN) // Issued by our CA cert
assertEquals(caCert.issuerDN, serverCert.issuerDN) // Issued by our CA cert
serverCert.checkValidity(Date()) // throws on verification problems
serverCert.verify(caCertAndKey.keyPair.public) // throws on verification problems
serverCert.verify(caKey.public) // throws on verification problems
assertFalse { serverCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
assertTrue { serverCert.basicConstraints == -1 } // This returns the signing path length should be -1 for non-CA certificate
assertEquals(2, serverCert.subjectAlternativeNames.size)
var foundAliasDnsName = false
for (entry in serverCert.subjectAlternativeNames) {
val typeId = entry[0] as Int
val value = entry[1] as String
if (typeId == GeneralName.iPAddress) {
assertEquals("10.0.0.54", value)
} else if (value == "alias name") {
foundAliasDnsName = true
}
}
assertTrue(foundAliasDnsName)
}
@Test
fun `storing EdDSA key in java keystore`() {
val tmpKeyStore = tempFile("keystore.jks")
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), Crypto.EDDSA_ED25519_SHA512)
val keyPair = generateKeyPair(EDDSA_ED25519_SHA512)
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
assertEquals(selfSignCert.certificate.publicKey, selfSignCert.keyPair.public)
assertEquals(selfSignCert.publicKey, keyPair.public)
// Save the EdDSA private key with self sign cert in the keystore.
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
keyStore.setKeyEntry("Key", selfSignCert.keyPair.private, "password".toCharArray(), arrayOf(selfSignCert.certificate))
keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), arrayOf(selfSignCert))
keyStore.save(tmpKeyStore, "keystorepass")
// Load the keystore from file and make sure keys are intact.
@ -97,20 +92,21 @@ class X509UtilitiesTest {
assertNotNull(pubKey)
assertNotNull(privateKey)
assertEquals(selfSignCert.keyPair.public, pubKey)
assertEquals(selfSignCert.keyPair.private, privateKey)
assertEquals(keyPair.public, pubKey)
assertEquals(keyPair.private, privateKey)
}
@Test
fun `signing EdDSA key with EcDSA certificate`() {
val tmpKeyStore = tempFile("keystore.jks")
val ecDSACert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"))
val edDSAKeypair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val edDSACert = X509Utilities.createTlsServerCert(X500Name("CN=TestEdDSA"), edDSAKeypair.public, ecDSACert, listOf("alias name"), listOf("10.0.0.54"))
val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val ecDSACert = createSelfSignedCACertificate(X500Name("CN=Test"), ecDSAKey)
val edDSAKeypair = generateKeyPair(EDDSA_ED25519_SHA512)
val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public)
// Save the EdDSA private key with cert chains.
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert.certificate, edDSACert))
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert, edDSACert))
keyStore.save(tmpKeyStore, "keystorepass")
// Load the keystore from file and make sure keys are intact.
@ -138,8 +134,8 @@ class X509UtilitiesTest {
// Load back generated root CA Cert and private key from keystore and check against copy in truststore
val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass")
val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY) as X509Certificate
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA, "keypass".toCharArray()) as PrivateKey
val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCaCert, rootCaFromTrustStore)
rootCaCert.checkValidity(Date())
@ -147,24 +143,25 @@ class X509UtilitiesTest {
// Now sign something with private key and verify against certificate public key
val testData = "12345".toByteArray()
val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
val caSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
// Load back generated intermediate CA Cert and private key
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) as X509Certificate
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA) as X509Certificate
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA, "keypass".toCharArray()) as PrivateKey
intermediateCaCert.checkValidity(Date())
intermediateCaCert.verify(rootCaCert.publicKey)
// Now sign something with private key and verify against certificate public key
val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
val intermediateSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
}
@Test
fun `create server certificate in keystore for SSL`() {
val tmpCAKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.jks")
val tmpSSLKeyStore = tempFile("sslkeystore.jks")
val tmpServerKeyStore = tempFile("serverkeystore.jks")
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
@ -176,30 +173,39 @@ class X509UtilitiesTest {
// Load signing intermediate CA cert
val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass")
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "cakeypass")
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cakeypass")
// Generate server cert and private key and populate another keystore suitable for SSL
X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
// Load back server certificate
val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass")
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY, "serverkeypass")
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass")
serverCertAndKey.certificate.checkValidity(Date())
serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey)
assertTrue { serverCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) }
// Load back server certificate
val sslKeyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass")
val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass")
sslCertAndKey.certificate.checkValidity(Date())
sslCertAndKey.certificate.verify(serverCertAndKey.certificate.publicKey)
assertTrue { sslCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) }
// Now sign something with private key and verify against certificate public key
val testData = "123456".toByteArray()
val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
val signature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
}
@Test
fun `create server cert and use in SSL socket`() {
val tmpCAKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.jks")
val tmpSSLKeyStore = tempFile("sslkeystore.jks")
val tmpServerKeyStore = tempFile("serverkeystore.jks")
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
@ -210,7 +216,8 @@ class X509UtilitiesTest {
"trustpass")
// Generate server cert and private key and populate another keystore suitable for SSL
val keyStore = X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name)
X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name)
val keyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass")
val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
val context = SSLContext.getInstance("TLS")
@ -235,7 +242,7 @@ class X509UtilitiesTest {
arrayOf("TLSv1.2"))
serverParams.wantClientAuth = true
serverParams.needClientAuth = true
serverParams.endpointIdentificationAlgorithm = "HTTPS" // enable hostname checking
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
serverSocket.sslParameters = serverParams
serverSocket.useClientMode = false
@ -247,7 +254,7 @@ class X509UtilitiesTest {
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"),
arrayOf("TLSv1.2"))
clientParams.endpointIdentificationAlgorithm = "HTTPS" // enable hostname checking
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
clientSocket.sslParameters = clientParams
clientSocket.useClientMode = true
// We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind
@ -284,8 +291,7 @@ class X509UtilitiesTest {
val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
val x500name = X500Name(peerX500Principal.name)
assertEquals(MEGA_CORP.name, x500name)
X509Utilities.validateCertificateChain(trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), *peerChain)
val output = DataOutputStream(clientSocket.outputStream)
output.writeUTF("Hello World")
var timeout = 0
@ -324,36 +330,40 @@ class X509UtilitiesTest {
trustStoreFilePath: Path,
trustStorePassword: String
): KeyStore {
val rootCA = X509Utilities.createSelfSignedCACert(X509Utilities.getDevX509Name("Corda Node Root CA"))
val intermediateCA = X509Utilities.createIntermediateCert(X509Utilities.getDevX509Name("Corda Node Intermediate CA"), rootCA)
val rootCAKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Node Root CA"), rootCAKey)
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X509Utilities.getDevX509Name("Corda Node Intermediate CA"), intermediateCAKeyPair.public)
val keyPass = keyPassword.toCharArray()
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, rootCA.keyPair.private, keyPass, arrayOf(rootCA.certificate))
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(rootCACert))
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY,
intermediateCA.keyPair.private,
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA,
intermediateCAKeyPair.private,
keyPass,
arrayOf(intermediateCA.certificate, rootCA.certificate))
arrayOf(intermediateCACert, rootCACert))
keyStore.save(keyStoreFilePath, storePassword)
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCA.certificate)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCA.certificate)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert)
trustStore.save(trustStoreFilePath, trustStorePassword)
return keyStore
}
@Test
fun `Get correct private key type from Keystore`() {
val keyPair = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), keyPair)
val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.certificate))
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert))
val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")

View File

@ -143,7 +143,7 @@ class KryoTests {
@Test
fun `serialize - deserialize X509Certififcate`() {
val expected = X509Utilities.createSelfSignedCACert(ALICE.name).certificate
val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val serialized = expected.serialize(kryo).bytes
val actual: X509Certificate = serialized.deserialize(kryo)
assertEquals(expected, actual)
@ -151,9 +151,10 @@ class KryoTests {
@Test
fun `serialize - deserialize X509CertPath`() {
val rootCA = X509Utilities.createSelfSignedCACert(ALICE.name)
val certificate = X509Utilities.createTlsServerCert(BOB.name, BOB_PUBKEY, rootCA, emptyList(), emptyList())
val expected = X509Utilities.createCertificatePath(rootCA, certificate, false).certPath
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY)
val expected = X509Utilities.createCertificatePath(rootCACert, certificate, revocationEnabled = false)
val serialized = expected.serialize(kryo).bytes
val actual: CertPath = serialized.deserialize(kryo)
assertEquals(expected, actual)

View File

@ -73,6 +73,11 @@ UNRELEASED
point we will support the ability for a node to have multiple versions of the same flow registered, enabling backwards
compatibility of CorDapp flows.
* The certificate hierarchy has been changed in order to allow corda node to sign keys with proper certificate chain.
* The corda node will now be issued a restricted client CA for identity/transaction key signing.
* TLS certificate are now stored in `sslkeystore.jks` and identity keys are stored in `nodekeystore.jks`
.. warning:: The old keystore will need to be removed when upgrading to this version.
Milestone 11.1
--------------

View File

@ -107,33 +107,38 @@ To run from the command line in Windows:
4. Run ``gradlew samples:attachment-demo:runSender`` in another terminal window to send the attachment. Now look at the other windows to
see the output of the demo
Raft Notary demo
----------------
Notary demo
-----------
This demo shows a party getting transactions notarised by a distributed `Raft <https://raft.github.io/>`_-based notary service.
The demo will start three distributed notary nodes, and two counterparty nodes. One of the counterparties will generate transactions
that transfer a self-issued asset to the other party and submit them for notarisation.
This demo shows a party getting transactions notarised by either a single-node or a distributed notary service.
All versions of the demo start two counterparty nodes.
One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit them for notarisation.
The `Raft <https://raft.github.io/>`_ version of the demo will start three distributed notary nodes.
The `BFT SMaRt <https://bft-smart.github.io/library/>`_ version of the demo will start four distributed notary nodes.
The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary,
every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement.
In the BFT SMaRt distributed notary, three signatures are required.
You will notice that successive transactions get signed by different members of the cluster (usually allocated in a random order).
To run from the command line in Unix:
To run the Raft version of the demo from the command line in Unix:
1. Run ``./gradlew samples:raft-notary-demo:deployNodes``, which will create node directories with configs under ``samples/raft-notary-demo/build/nodes``.
2. Run ``./samples/raft-notary-demo/build/nodes/runnodes``, which will start the nodes in separate terminal windows/tabs.
1. Run ``./gradlew samples:notary-demo:deployNodesRaft``, which will create node directories with configs under ``samples/notary-demo/build/nodes``.
2. Run ``./samples/notary-demo/build/nodes/runnodes``, which will start the nodes in separate terminal windows/tabs.
Wait until a "Node started up and registered in ..." message appears on each of the terminals
3. Run ``./gradlew samples:raft-notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys
To run from the command line in Windows:
1. Run ``gradlew samples:raft-notary-demo:deployNodes``, which will create node directories with configs under ``samples\raft-notary-demo\build\nodes``.
2. Run ``samples\raft-notary-demo\build\nodes\runnodes``, which will start the nodes in separate terminal windows/tabs.
1. Run ``gradlew samples:notary-demo:deployNodesRaft``, which will create node directories with configs under ``samples\notary-demo\build\nodes``.
2. Run ``samples\notary-demo\build\nodes\runnodes``, which will start the nodes in separate terminal windows/tabs.
Wait until a "Node started up and registered in ..." message appears on each of the terminals
3. Run ``gradlew samples:raft-notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys
To run the BFT SMaRt notary demo, use ``deployNodesBFT`` instead of ``deployNodesRaft``. For a single notary node, use ``deployNodesSingle``.
Notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node.
You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores
by using the H2 web console:

View File

@ -11,15 +11,15 @@ import net.corda.core.crypto.*
import net.corda.core.crypto.KeyStoreUtilities.loadKeyStore
import net.corda.core.crypto.KeyStoreUtilities.loadOrCreateKeyStore
import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY
import net.corda.core.crypto.X509Utilities.createIntermediateCert
import net.corda.core.crypto.X509Utilities.createTlsServerCert
import net.corda.core.crypto.X509Utilities.createCertificate
import net.corda.core.seconds
import net.corda.core.utilities.loggerFor
import net.corda.node.utilities.configureDatabase
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
@ -83,8 +83,7 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CertificateAnd
for (id in storage.getApprovedRequestIds()) {
storage.approveRequest(id) {
val request = JcaPKCS10CertificationRequest(request)
createTlsServerCert(request.subject, request.publicKey, caCertAndKey,
if (ipAddress == hostName) listOf() else listOf(hostName), listOf(ipAddress))
createCertificate(CertificateType.CLIENT_CA, caCertAndKey.certificate, caCertAndKey.keyPair, request.subject, request.publicKey, nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))), arrayOf()))
}
logger.info("Approved request $id")
serverStatus.lastApprovalTime = Instant.now()
@ -137,19 +136,20 @@ private fun DoormanParameters.generateRootKeyPair() {
val rootStore = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword)
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ")
if (rootStore.containsAlias(CORDA_ROOT_CA_PRIVATE_KEY)) {
val oldKey = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA_PRIVATE_KEY).publicKey
println("Key $CORDA_ROOT_CA_PRIVATE_KEY already exists in keystore, process will now terminate.")
if (rootStore.containsAlias(CORDA_ROOT_CA)) {
val oldKey = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA).publicKey
println("Key $CORDA_ROOT_CA already exists in keystore, process will now terminate.")
println(oldKey)
exitProcess(1)
}
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name(CORDA_ROOT_CA))
rootStore.addOrReplaceKey(CORDA_ROOT_CA_PRIVATE_KEY, selfSignCert.keyPair.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate))
val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name(CORDA_ROOT_CA), selfSignKey)
rootStore.addOrReplaceKey(CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert))
rootStore.save(rootStorePath, rootKeystorePassword)
println("Root CA keypair and certificate stored in $rootStorePath.")
println(loadKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA_PRIVATE_KEY).publicKey)
println(loadKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA).publicKey)
}
private fun DoormanParameters.generateCAKeyPair() {
@ -159,7 +159,7 @@ private fun DoormanParameters.generateCAKeyPair() {
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ")
val rootKeyStore = loadKeyStore(rootStorePath, rootKeystorePassword)
val rootKeyAndCert = rootKeyStore.getCertificateAndKeyPair(rootPrivateKeyPassword, CORDA_ROOT_CA_PRIVATE_KEY)
val rootKeyAndCert = rootKeyStore.getCertificateAndKeyPair(rootPrivateKeyPassword, CORDA_ROOT_CA)
val keystorePassword = keystorePassword ?: readPassword("Keystore Password: ")
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ")
@ -167,19 +167,20 @@ private fun DoormanParameters.generateCAKeyPair() {
keystorePath.parent.createDirectories()
val keyStore = loadOrCreateKeyStore(keystorePath, keystorePassword)
if (keyStore.containsAlias(CORDA_INTERMEDIATE_CA_PRIVATE_KEY)) {
val oldKey = loadOrCreateKeyStore(keystorePath, rootKeystorePassword).getCertificate(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).publicKey
println("Key $CORDA_INTERMEDIATE_CA_PRIVATE_KEY already exists in keystore, process will now terminate.")
if (keyStore.containsAlias(CORDA_INTERMEDIATE_CA)) {
val oldKey = loadOrCreateKeyStore(keystorePath, rootKeystorePassword).getCertificate(CORDA_INTERMEDIATE_CA).publicKey
println("Key $CORDA_INTERMEDIATE_CA already exists in keystore, process will now terminate.")
println(oldKey)
exitProcess(1)
}
val intermediateKeyAndCert = createIntermediateCert(X500Name(CORDA_INTERMEDIATE_CA), rootKeyAndCert)
keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, intermediateKeyAndCert.keyPair.private,
caPrivateKeyPassword.toCharArray(), arrayOf(intermediateKeyAndCert.certificate, rootKeyAndCert.certificate))
val intermediateKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCert = createCertificate(CertificateType.INTERMEDIATE_CA, rootKeyAndCert.certificate, rootKeyAndCert.keyPair, X500Name(CORDA_INTERMEDIATE_CA), intermediateKey.public)
keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA, intermediateKey.private,
caPrivateKeyPassword.toCharArray(), arrayOf(intermediateCert, rootKeyAndCert.certificate))
keyStore.save(keystorePath, keystorePassword)
println("Intermediate CA keypair and certificate stored in $keystorePath.")
println(loadKeyStore(keystorePath, keystorePassword).getCertificate(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).publicKey)
println(loadKeyStore(keystorePath, keystorePassword).getCertificate(CORDA_INTERMEDIATE_CA).publicKey)
}
private fun DoormanParameters.startDoorman() {
@ -189,8 +190,8 @@ private fun DoormanParameters.startDoorman() {
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ")
val keystore = loadOrCreateKeyStore(keystorePath, keystorePassword)
val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).last()
val caCertAndKey = keystore.getCertificateAndKeyPair(caPrivateKeyPassword, CORDA_INTERMEDIATE_CA_PRIVATE_KEY)
val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA).last()
val caCertAndKey = keystore.getCertificateAndKeyPair(caPrivateKeyPassword, CORDA_INTERMEDIATE_CA)
// Create DB connection.
val (datasource, database) = configureDatabase(dataSourceProperties)

View File

@ -5,10 +5,7 @@ import com.nhaarman.mockito_kotlin.*
import com.r3.corda.doorman.persistence.CertificateResponse
import com.r3.corda.doorman.persistence.CertificationRequestData
import com.r3.corda.doorman.persistence.CertificationRequestStorage
import net.corda.core.crypto.CertificateStream
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.*
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThat
@ -29,12 +26,14 @@ import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals
class DoormanServiceTest {
private val rootCA = X509Utilities.createSelfSignedCACert(X500Name("CN=Corda Node Root CA,L=London"))
private val intermediateCA = X509Utilities.createSelfSignedCACert(X500Name("CN=Corda Node Intermediate CA,L=London"))
private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val rootCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Root CA,L=London"), rootCAKey)
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val intermediateCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey)
private lateinit var doormanServer: DoormanServer
private fun startSigningServer(storage: CertificationRequestStorage) {
doormanServer = DoormanServer(HostAndPort.fromParts("localhost", 0), intermediateCA, rootCA.certificate, storage)
doormanServer = DoormanServer(HostAndPort.fromParts("localhost", 0), CertificateAndKeyPair(intermediateCACert, intermediateCAKey), rootCACert, storage)
doormanServer.start()
}
@ -90,8 +89,7 @@ class DoormanServiceTest {
storage.approveRequest(id) {
JcaPKCS10CertificationRequest(request).run {
X509Utilities.createTlsServerCert(subject, publicKey, intermediateCA,
if (ipAddress == hostName) listOf() else listOf(hostName), listOf(ipAddress))
X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey)
}
}

View File

@ -3,6 +3,7 @@ package com.r3.corda.doorman.internal.persistence
import com.r3.corda.doorman.persistence.CertificateResponse
import com.r3.corda.doorman.persistence.CertificationRequestData
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
import net.corda.core.crypto.CertificateType
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
@ -21,7 +22,8 @@ import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class DBCertificateRequestStorageTest {
private val intermediateCA = X509Utilities.createSelfSignedCACert(X500Name("CN=Corda Node Intermediate CA"))
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val intermediateCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Intermediate CA"), intermediateCAKey)
private var closeDb: Closeable? = null
private lateinit var storage: DBCertificateRequestStorage
@ -137,12 +139,12 @@ class DBCertificateRequestStorageTest {
private fun approveRequest(requestId: String) {
storage.approveRequest(requestId) {
JcaPKCS10CertificationRequest(request).run {
X509Utilities.createTlsServerCert(
X509Utilities.createCertificate(
CertificateType.TLS,
intermediateCACert,
intermediateCAKey,
subject,
publicKey,
intermediateCA,
if (ipAddress == hostName) listOf() else listOf(hostName),
listOf(ipAddress))
publicKey)
}
}
}

View File

@ -107,8 +107,10 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
*/
fun checkStorePasswords() {
val config = config ?: return
config.keyStoreFile.read {
KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray())
arrayOf(config.sslKeystore, config.nodeKeystore).forEach {
it.read {
KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray())
}
}
config.trustStoreFile.read {
KeyStore.getInstance("JKS").load(it, config.trustStorePassword.toCharArray())

View File

@ -53,14 +53,14 @@ class ArtemisTcpTransport {
)
if (config != null && enableSSL) {
config.keyStoreFile.expectedOnDefaultFileSystem()
config.sslKeystore.expectedOnDefaultFileSystem()
config.trustStoreFile.expectedOnDefaultFileSystem()
val tlsOptions = mapOf<String, Any?>(
val tlsOptions = mapOf(
// Enable TLS transport layer with client certs and restrict to at least SHA256 in handshake
// and AES encryption
TransportConstants.SSL_ENABLED_PROP_NAME to true,
TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS",
TransportConstants.KEYSTORE_PATH_PROP_NAME to config.keyStoreFile,
TransportConstants.KEYSTORE_PATH_PROP_NAME to config.sslKeystore,
TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to config.keyStorePassword, // TODO proper management of keystores and password
TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to "JKS",
TransportConstants.TRUSTSTORE_PATH_PROP_NAME to config.trustStoreFile,

View File

@ -7,6 +7,7 @@ interface SSLConfiguration {
val keyStorePassword: String
val trustStorePassword: String
val certificatesDirectory: Path
val keyStoreFile: Path get() = certificatesDirectory / "sslkeystore.jks"
val sslKeystore: Path get() = certificatesDirectory / "sslkeystore.jks"
val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks"
val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks"
}

View File

@ -1,36 +0,0 @@
# Copyright (c) 2007-2013 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This file defines the replicas ids, IPs and ports.
# It is used by the replicas and clients to find connection info
# to the initial replicas.
# The ports defined here are the ports used by clients to communicate
# with the replicas. Additional connections are opened by replicas to
# communicate with each other. This additional connection is opened in the
# next port defined here. For an example, consider the line "0 127.0.0.1 11000".
# That means that clients will open a communication channel to replica 0 in
# IP 127.0.0.1 and port 11000. On startup, replicas with id different than 0
# will open a communication channel to replica 0 in port 11001.
# The same holds for replicas 1, 2, 3 ... N.
#server id, address and port (the ids from 0 to n-1 are the service replicas)
0 127.0.0.1 11000
1 127.0.0.1 11010
2 127.0.0.1 11020
3 127.0.0.1 11030
4 127.0.0.1 11040
5 127.0.0.1 11050
6 127.0.0.1 11060
7 127.0.0.1 11070
7001 127.0.0.1 11100

View File

@ -40,7 +40,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
// javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"]
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"]
systemProperties['visualvm.display.name'] = 'Corda'
systemProperties['jdk.serialFilter'] = 'maxbytes=0'
minJavaVersion = '1.8.0'
minUpdateVersion['1.8'] = java8_minUpdateVersion
caplets = ['CordaCaplet']

View File

@ -53,7 +53,6 @@ class BootTests {
class ObjectInputStreamFlow : FlowLogic<Unit>() {
@Suspendable
override fun call() {
System.clearProperty("jdk.serialFilter") // This checks that the node has already consumed the property.
val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
ObjectInputStream(data.inputStream()).use { it.readObject() }
}

View File

@ -5,19 +5,19 @@ import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionType
import net.corda.core.crypto.appendToCommonName
import net.corda.core.crypto.commonName
import net.corda.core.div
import net.corda.core.getOrThrow
import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.flows.NotaryError
import net.corda.flows.NotaryException
import net.corda.flows.NotaryFlow
import net.corda.node.internal.AbstractNode
import net.corda.node.internal.Node
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.node.utilities.transaction
import net.corda.testing.node.NodeBasedTest
@ -28,71 +28,55 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class BFTNotaryServiceTests : NodeBasedTest() {
private companion object {
val notaryCommonName = X500Name("CN=BFT Notary Server,O=R3,OU=corda,L=Zurich,C=CH")
fun buildNodeName(it: Int, notaryName: X500Name): X500Name {
return notaryName.appendToCommonName("-$it")
}
}
@Test
fun `detect double spend`() {
val masterNode = startBFTNotaryCluster(notaryCommonName, 4, BFTNonValidatingNotaryService.type).first()
val clusterName = X500Name("CN=BFT,O=R3,OU=corda,L=Zurich,C=CH")
startBFTNotaryCluster(clusterName, 4, BFTNonValidatingNotaryService.type)
val alice = startNode(ALICE.name).getOrThrow()
val notaryParty = alice.netMapCache.getNotary(notaryCommonName)!!
val notaryParty = alice.netMapCache.getNotary(clusterName)!!
val inputState = issueState(alice, notaryParty)
val firstTxBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState)
val firstSpendTx = alice.services.signInitialTransaction(firstTxBuilder)
val firstSpend = alice.services.startFlow(NotaryFlow.Client(firstSpendTx))
firstSpend.resultFuture.getOrThrow()
val secondSpendBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState).run {
val dummyState = DummyContract.SingleOwnerState(0, alice.info.legalIdentity)
addOutputState(dummyState)
this
alice.services.startFlow(NotaryFlow.Client(firstSpendTx)).resultFuture.getOrThrow()
val secondSpendBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState).also {
it.addOutputState(DummyContract.SingleOwnerState(0, alice.info.legalIdentity))
}
val secondSpendTx = alice.services.signInitialTransaction(secondSpendBuilder)
val secondSpend = alice.services.startFlow(NotaryFlow.Client(secondSpendTx))
val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() }
val ex = assertFailsWith(NotaryException::class) {
secondSpend.resultFuture.getOrThrow()
}
val error = ex.error as NotaryError.Conflict
assertEquals(error.txId, secondSpendTx.id)
}
private fun issueState(node: AbstractNode, notary: Party): StateAndRef<*> {
return node.database.transaction {
val builder = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0))
val stx = node.services.signInitialTransaction(builder)
node.services.recordTransactions(listOf(stx))
private fun issueState(node: AbstractNode, notary: Party) = node.run {
database.transaction {
val builder = DummyContract.generateInitial(Random().nextInt(), notary, info.legalIdentity.ref(0))
val stx = services.signInitialTransaction(builder)
services.recordTransactions(listOf(stx))
StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0))
}
}
private fun startBFTNotaryCluster(notaryName: X500Name,
private fun startBFTNotaryCluster(clusterName: X500Name,
clusterSize: Int,
serviceType: ServiceType): List<Node> {
serviceType: ServiceType) {
require(clusterSize > 0)
val quorum = (2 * clusterSize + 1) / 3
val replicaNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
ServiceIdentityGenerator.generateToDisk(
(0 until clusterSize).map { tempFolder.root.toPath() / "${notaryName.commonName}-$it" },
replicaNames.map { baseDirectory(it) },
serviceType.id,
notaryName,
quorum)
val serviceInfo = ServiceInfo(serviceType, notaryName)
val nodes = (0 until clusterSize).map {
clusterName,
minCorrectReplicas(clusterSize))
val serviceInfo = ServiceInfo(serviceType, clusterName)
val notaryClusterAddresses = (0 until clusterSize).map { "localhost:${11000 + it * 10}" }
(0 until clusterSize).forEach {
startNode(
buildNodeName(it, notaryName),
replicaNames[it],
advertisedServices = setOf(serviceInfo),
configOverrides = mapOf("notaryNodeId" to it)
configOverrides = mapOf("bftReplicaId" to it, "notaryClusterAddresses" to notaryClusterAddresses)
).getOrThrow()
}
return nodes
}
}

View File

@ -1,14 +1,26 @@
package net.corda.services.messaging
import net.corda.core.copyTo
import net.corda.core.createDirectories
import net.corda.core.crypto.*
import net.corda.core.exists
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.testing.MEGA_CORP
import net.corda.testing.MINI_CORP
import net.corda.testing.messaging.SimpleMQClient
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.junit.Test
import java.nio.file.Files
/**
* Runs the security tests with the attacker pretending to be a node on the network.
@ -66,4 +78,56 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
attacker.start(PEER_USER, PEER_USER, enableSSL = false) // Login as a peer
}
}
@Test
fun `login with invalid certificate chain`() {
val sslConfig = object : SSLConfiguration {
override val certificatesDirectory = Files.createTempDirectory("certs")
override val keyStorePassword: String get() = "cordacadevpass"
override val trustStorePassword: String get() = "trustpass"
init {
val legalName = MEGA_CORP.name
certificatesDirectory.createDirectories()
if (!trustStoreFile.exists()) {
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
}
val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
val intermediateCA = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val clientKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
// Set name constrain to the legal name.
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf())
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCA.certificate, intermediateCA.keyPair, legalName, clientKey.public, nameConstraints = nameConstraints)
val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
// Using different x500 name in the TLS cert which is not allowed in the name constraints.
val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, MINI_CORP.name, tlsKey.public)
val keyPass = keyStorePassword.toCharArray()
val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(nodeKeystore, keyStorePassword)
clientCAKeystore.addOrReplaceKey(
X509Utilities.CORDA_CLIENT_CA,
clientKey.private,
keyPass,
arrayOf(clientCACert, intermediateCA.certificate, rootCACert))
clientCAKeystore.save(nodeKeystore, keyStorePassword)
val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeystore, keyStorePassword)
tlsKeystore.addOrReplaceKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKey.private,
keyPass,
arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))
tlsKeystore.save(sslKeystore, keyStorePassword)
}
}
val attacker = clientTo(alice.configuration.p2pAddress, sslConfig)
assertThatExceptionOfType(ActiveMQNotConnectedException::class.java).isThrownBy {
attacker.start(PEER_USER, PEER_USER)
}
}
}

View File

@ -3,8 +3,6 @@ package net.corda.services.messaging
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.*
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.commonName
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.services.DEFAULT_SESSION_ID
@ -64,10 +62,8 @@ class P2PMessagingTest : NodeBasedTest() {
// TODO Use a dummy distributed service
@Test
fun `communicating with a distributed service which the network map node is part of`() {
val root = tempFolder.root.toPath()
ServiceIdentityGenerator.generateToDisk(
listOf(root / DUMMY_MAP.name.commonName, root / SERVICE_2_NAME.commonName),
listOf(DUMMY_MAP.name, SERVICE_2_NAME).map { baseDirectory(it) },
RaftValidatingNotaryService.type.id,
DISTRIBUTED_SERVICE_NAME)

View File

@ -2,8 +2,6 @@ package net.corda.services.messaging
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.commonName
import net.corda.core.div
import net.corda.core.getOrThrow
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
@ -60,7 +58,7 @@ class P2PSecurityTest : NodeBasedTest() {
private fun startSimpleNode(legalName: X500Name): SimpleNode {
val config = TestNodeConfiguration(
baseDirectory = tempFolder.root.toPath() / legalName.commonName,
baseDirectory = baseDirectory(legalName),
myLegalName = legalName,
networkMapService = NetworkMapInfo(networkMapNode.configuration.p2pAddress, networkMapNode.info.legalIdentity.name))
config.configureWithDevSSLCertificate() // This creates the node's TLS cert with the CN as the legal name

View File

@ -10,10 +10,10 @@ import net.corda.core.crypto.commonName
import net.corda.core.crypto.orgName
import net.corda.core.node.VersionInfo
import net.corda.core.utilities.Emoji
import net.corda.core.utilities.LogHelper.withLevel
import net.corda.node.internal.Node
import net.corda.node.internal.enforceSingleNodeIsRunning
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.transactions.bftSMaRtSerialFilter
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
@ -21,7 +21,6 @@ import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole
import org.slf4j.LoggerFactory
import org.slf4j.bridge.SLF4JBridgeHandler
import java.io.*
import java.lang.management.ManagementFactory
import java.net.InetAddress
import java.nio.file.Paths
@ -72,8 +71,6 @@ fun main(args: Array<String>) {
enforceSingleNodeIsRunning(cmdlineOptions.baseDirectory)
initLogging(cmdlineOptions)
disableJavaDeserialization() // Should be after initLogging to avoid TMI.
// Manifest properties are only available if running from the corda jar
fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
@ -107,7 +104,7 @@ fun main(args: Array<String>) {
println("Unable to load the configuration file: ${e.rootCause.message}")
exitProcess(2)
}
SerialFilter.install(if (conf.bftReplicaId != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
if (cmdlineOptions.isRegistration) {
println()
println("******************************************************************")
@ -208,29 +205,12 @@ private fun assertCanNormalizeEmptyPath() {
}
}
private fun failStartUp(message: String): Nothing {
internal fun failStartUp(message: String): Nothing {
println(message)
println("Corda will now exit...")
exitProcess(1)
}
private fun disableJavaDeserialization() {
// ObjectInputFilter and friends are in java.io in Java 9 but sun.misc in backports, so we are using the system property interface for portability.
// This property has already been set in the Capsule. Anywhere else may be too late, but we'll repeat it here for developers.
System.setProperty("jdk.serialFilter", "maxbytes=0")
// Attempt at deserialization so that ObjectInputFilter (permanently) inits itself:
val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
try {
withLevel("java.io.serialization", "WARN") {
ObjectInputStream(data.inputStream()).use { it.readObject() } // Logs REJECTED at INFO, which we don't want users to see.
}
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
failStartUp("Corda forbids Java deserialisation. Please upgrade to at least JDK 8u121 and set system property 'jdk.serialFilter' to 'maxbytes=0' when booting Corda.")
} catch (e: InvalidClassException) {
// Good, our system property is honoured.
}
}
private fun printPluginsAndServices(node: Node) {
node.configuration.extraAdvertisedServiceIds.let {
if (it.isNotEmpty()) printBasicNodeInfo("Providing network services", it.joinToString())

View File

@ -0,0 +1,62 @@
package net.corda.node
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Proxy
internal object SerialFilter {
private val filterInterface: Class<*>
private val serialClassGetter: Method
private val undecided: Any
private val rejected: Any
private val serialFilterLock: Any
private val serialFilterField: Field
init {
// ObjectInputFilter and friends are in java.io in Java 9 but sun.misc in backports:
fun getFilterInterface(packageName: String): Class<*>? {
return try {
Class.forName("$packageName.ObjectInputFilter")
} catch (e: ClassNotFoundException) {
null
}
}
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
filterInterface = getFilterInterface("java.io")
?: getFilterInterface("sun.misc")
?: failStartUp("Corda forbids Java deserialisation. Please upgrade to at least JDK 8u121.")
serialClassGetter = Class.forName("${filterInterface.name}\$FilterInfo").getMethod("serialClass")
val statusEnum = Class.forName("${filterInterface.name}\$Status")
undecided = statusEnum.getField("UNDECIDED").get(null)
rejected = statusEnum.getField("REJECTED").get(null)
val configClass = Class.forName("${filterInterface.name}\$Config")
serialFilterLock = configClass.getDeclaredField("serialFilterLock").also { it.isAccessible = true }.get(null)
serialFilterField = configClass.getDeclaredField("serialFilter").also { it.isAccessible = true }
}
internal fun install(acceptClass: (Class<*>) -> Boolean) {
val filter = Proxy.newProxyInstance(javaClass.classLoader, arrayOf(filterInterface)) { _, _, args ->
val serialClass = serialClassGetter.invoke(args[0]) as Class<*>?
if (applyPredicate(acceptClass, serialClass)) {
undecided
} else {
rejected
}
}
// Can't simply use the setter as in non-trampoline mode Capsule has inited the filter in premain:
synchronized(serialFilterLock) {
serialFilterField.set(null, filter)
}
}
internal fun applyPredicate(acceptClass: (Class<*>) -> Boolean, serialClass: Class<*>?): Boolean {
// Similar logic to jdk.serialFilter, our concern is side-effects at deserialisation time:
if (null == serialClass) return true
var componentType: Class<*> = serialClass
while (componentType.isArray) componentType = componentType.componentType
if (componentType.isPrimitive) return true
return acceptClass(componentType)
}
}
internal fun defaultSerialFilter(@Suppress("UNUSED_PARAMETER") clazz: Class<*>) = false

View File

@ -32,6 +32,8 @@ import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.config.parseAs
import net.corda.cordform.CordformNode
import net.corda.cordform.CordformContext
import net.corda.core.internal.ShutdownHook
import net.corda.core.internal.addShutdownHook
import okhttp3.OkHttpClient
import okhttp3.Request
import org.bouncycastle.asn1.x500.X500Name
@ -236,22 +238,19 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
coerce: (D) -> DI,
dsl: DI.() -> A
): A {
var shutdownHook: Thread? = null
var shutdownHook: ShutdownHook? = null
try {
driverDsl.start()
shutdownHook = Thread({
shutdownHook = addShutdownHook {
driverDsl.shutdown()
})
Runtime.getRuntime().addShutdownHook(shutdownHook)
}
return dsl(coerce(driverDsl))
} catch (exception: Throwable) {
log.error("Driver shutting down because of exception", exception)
throw exception
} finally {
driverDsl.shutdown()
if (shutdownHook != null) {
Runtime.getRuntime().removeShutdownHook(shutdownHook)
}
shutdownHook?.cancel()
}
}
@ -558,21 +557,19 @@ class DriverDSL(
verifierType: VerifierType,
rpcUsers: List<User>
): ListenableFuture<Pair<Party, List<NodeHandle>>> {
val nodeNames = (1..clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(it.toString()) }
val nodeNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
val paths = nodeNames.map { baseDirectory(it) }
ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName)
val serviceInfo = ServiceInfo(type, notaryName)
val advertisedService = setOf(serviceInfo)
val advertisedServices = setOf(ServiceInfo(type, notaryName))
val notaryClusterAddress = portAllocation.nextHostAndPort()
// Start the first node that will bootstrap the cluster
val firstNotaryFuture = startNode(nodeNames.first(), advertisedService, rpcUsers, verifierType, mapOf("notaryNodeAddress" to notaryClusterAddress.toString()))
val firstNotaryFuture = startNode(nodeNames.first(), advertisedServices, rpcUsers, verifierType, mapOf("notaryNodeAddress" to notaryClusterAddress.toString()))
// All other nodes will join the cluster
val restNotaryFutures = nodeNames.drop(1).map {
val nodeAddress = portAllocation.nextHostAndPort()
val configOverride = mapOf("notaryNodeAddress" to nodeAddress.toString(), "notaryClusterAddresses" to listOf(notaryClusterAddress.toString()))
startNode(it, advertisedService, rpcUsers, verifierType, configOverride)
startNode(it, advertisedServices, rpcUsers, verifierType, configOverride)
}
return firstNotaryFuture.flatMap { firstNotary ->

View File

@ -61,6 +61,7 @@ import org.jetbrains.exposed.sql.Database
import org.slf4j.Logger
import java.io.IOException
import java.lang.reflect.Modifier.*
import java.net.InetAddress
import java.net.URL
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
@ -390,7 +391,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
protected open fun makeServiceEntries(): List<ServiceEntry> {
return advertisedServices.map {
val serviceId = it.type.id
val serviceName = it.name ?: configuration.myLegalName.replaceCommonName(serviceId)
val serviceName = it.name ?: X500Name("${configuration.myLegalName},OU=$serviceId")
val identity = obtainKeyPair(serviceId, serviceName).first
ServiceEntry(it, identity)
}
@ -400,16 +401,16 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
protected open fun acceptableLiveFiberCountOnStop(): Int = 0
private fun hasSSLCertificates(): Boolean {
val keyStore = try {
val (sslKeystore, keystore) = try {
// This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
KeyStoreUtilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword)
Pair(KeyStoreUtilities.loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword), KeyStoreUtilities.loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword))
} catch (e: IOException) {
null
return false
} catch (e: KeyStoreException) {
log.warn("Certificate key store found but key store password does not match configuration.")
null
return false
}
return keyStore?.containsAlias(X509Utilities.CORDA_CLIENT_CA) ?: false
return sslKeystore.containsAlias(X509Utilities.CORDA_CLIENT_TLS) && keystore.containsAlias(X509Utilities.CORDA_CLIENT_CA)
}
// Specific class so that MockNode can catch it.
@ -518,10 +519,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
RaftNonValidatingNotaryService.type -> RaftNonValidatingNotaryService(timestampChecker, uniquenessProvider as RaftUniquenessProvider)
RaftValidatingNotaryService.type -> RaftValidatingNotaryService(timestampChecker, uniquenessProvider as RaftUniquenessProvider)
BFTNonValidatingNotaryService.type -> with(configuration as FullNodeConfiguration) {
val nodeId = notaryNodeId ?: throw IllegalArgumentException("notaryNodeId value must be specified in the configuration")
val client = BFTSMaRt.Client(nodeId)
tokenizableServices += client
BFTNonValidatingNotaryService(services, timestampChecker, nodeId, database, client)
val replicaId = bftReplicaId ?: throw IllegalArgumentException("bftReplicaId value must be specified in the configuration")
BFTSMaRtConfig(notaryClusterAddresses).use { config ->
val client = BFTSMaRt.Client(config, replicaId).also { tokenizableServices += it } // (Ab)use replicaId for clientId.
BFTNonValidatingNotaryService(config, services, timestampChecker, replicaId, database, client)
}
}
else -> {
throw IllegalArgumentException("Notary type ${type.id} is not handled by makeNotaryService.")
@ -598,12 +600,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
// the legal name is actually validated in some way.
// TODO: Integrate with Key management service?
val keystore = KeyStoreUtilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword)
val keystore = KeyStoreUtilities.loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword)
val clientCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, configuration.keyStorePassword)
val privateKeyAlias = "$serviceId-private-key"
val privKeyFile = configuration.baseDirectory / privateKeyAlias
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
val identityAndKey = if (configuration.keyStoreFile.exists() && keystore.containsAlias(privateKeyAlias)) {
val identityAndKey = if (configuration.nodeKeystore.exists() && keystore.containsAlias(privateKeyAlias)) {
// Get keys from keystore.
val (cert, keyPair) = keystore.getCertificateAndKeyPair(privateKeyAlias, configuration.keyStorePassword)
val loadedServiceName = X509CertificateHolder(cert.encoded).subject
@ -624,19 +627,18 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
"$serviceName vs ${myIdentity.name}")
// Load the private key.
val keyPair = privKeyFile.readAll().deserialize<KeyPair>()
// TODO: Use a proper certificate chain.
val selfSignCert = X509Utilities.createSelfSignedCACert(serviceName, keyPair)
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(selfSignCert.certificate))
keystore.save(configuration.keyStoreFile, configuration.keyStorePassword)
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(cert, *keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
keystore.save(configuration.nodeKeystore, configuration.keyStorePassword)
Pair(myIdentity, keyPair)
} else {
// Create new keys and store in keystore.
log.info("Identity key not found, generating fresh key!")
val keyPair: KeyPair = generateKeyPair()
val selfSignCert = X509Utilities.createSelfSignedCACert(serviceName, keyPair)
keystore.addOrReplaceKey(privateKeyAlias, selfSignCert.keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(selfSignCert.certificate))
keystore.save(configuration.keyStoreFile, configuration.keyStorePassword)
Pair(Party(serviceName, selfSignCert.keyPair.public), selfSignCert.keyPair)
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(cert, *keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
keystore.save(configuration.nodeKeystore, configuration.keyStorePassword)
Pair(Party(serviceName, keyPair.public), keyPair)
}
partyKeys += identityAndKey.second
return identityAndKey

View File

@ -1,7 +1,7 @@
package net.corda.node.internal
import net.corda.core.internal.addShutdownHook
import net.corda.core.div
import net.corda.core.utilities.loggerFor
import java.io.RandomAccessFile
import java.lang.management.ManagementFactory
import java.nio.file.Path
@ -26,9 +26,9 @@ fun enforceSingleNodeIsRunning(baseDirectory: Path) {
}
// Avoid the lock being garbage collected. We don't really need to release it as the OS will do so for us
// when our process shuts down, but we try in stop() anyway just to be nice.
Runtime.getRuntime().addShutdownHook(Thread {
addShutdownHook {
pidFileLock.release()
})
}
val ourProcessID: String = ManagementFactory.getRuntimeMXBean().name.split("@")[0]
pidFileRw.setLength(0)
pidFileRw.write(ourProcessID.toByteArray())

View File

@ -5,16 +5,15 @@ import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.flatMap
import net.corda.core.*
import net.corda.core.internal.ShutdownHook
import net.corda.core.internal.addShutdownHook
import net.corda.core.messaging.RPCOps
import net.corda.core.minutes
import net.corda.core.node.ServiceHub
import net.corda.core.node.VersionInfo
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.seconds
import net.corda.core.success
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import net.corda.node.printBasicNodeInfo
@ -47,7 +46,6 @@ import java.io.IOException
import java.time.Clock
import java.util.*
import javax.management.ObjectName
import kotlin.concurrent.thread
/**
* A Node manages a standalone server that takes part in the P2P network. It creates the services found in [ServiceHub],
@ -112,7 +110,7 @@ class Node(override val configuration: FullNodeConfiguration,
var messageBroker: ArtemisMessagingServer? = null
private var shutdownThread: Thread? = null
private var shutdownHook: ShutdownHook? = null
private lateinit var userService: RPCUserService
@ -295,12 +293,9 @@ class Node(override val configuration: FullNodeConfiguration,
(startupComplete as SettableFuture<Unit>).set(Unit)
}
shutdownThread = thread(start = false) {
shutdownHook = addShutdownHook {
stop()
}
Runtime.getRuntime().addShutdownHook(shutdownThread)
return this
}
@ -322,12 +317,9 @@ class Node(override val configuration: FullNodeConfiguration,
synchronized(this) {
if (shutdown) return
shutdown = true
// Unregister shutdown hook to prevent any unnecessary second calls to stop
if ((shutdownThread != null) && (Thread.currentThread() != shutdownThread)) {
Runtime.getRuntime().removeShutdownHook(shutdownThread)
shutdownThread = null
}
shutdownHook?.cancel()
shutdownHook = null
}
printBasicNodeInfo("Shutting down ...")

View File

@ -54,8 +54,8 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: X500Name) {
if (!trustStoreFile.exists()) {
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
}
if (!keyStoreFile.exists()) {
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
X509Utilities.createKeystoreForSSL(keyStoreFile, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
X509Utilities.createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
}
}

View File

@ -62,7 +62,7 @@ data class FullNodeConfiguration(
// Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one
val messagingServerAddress: HostAndPort?,
val extraAdvertisedServiceIds: List<String>,
val notaryNodeId: Int?,
val bftReplicaId: Int?,
val notaryNodeAddress: HostAndPort?,
val notaryClusterAddresses: List<HostAndPort>,
override val certificateChainCheckPolicies: List<CertChainPolicyConfig>,

View File

@ -6,7 +6,7 @@ import com.google.common.util.concurrent.SettableFuture
import io.netty.handler.ssl.SslHandler
import net.corda.core.*
import net.corda.core.crypto.*
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
@ -30,10 +30,7 @@ import org.apache.activemq.artemis.core.config.Configuration
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
import org.apache.activemq.artemis.core.remoting.impl.netty.*
import org.apache.activemq.artemis.core.security.Role
import org.apache.activemq.artemis.core.server.ActiveMQServer
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
@ -46,8 +43,11 @@ import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateCallback
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
import org.apache.activemq.artemis.utils.ConfigurationHelper
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import rx.Subscription
import sun.security.x509.X509CertImpl
import java.io.IOException
import java.math.BigInteger
import java.security.KeyStore
@ -259,9 +259,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
@Throws(IOException::class, KeyStoreException::class)
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
val keyStore = KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword)
val keyStore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword)
val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_CA)
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_TLS)
val ourSubjectDN = X500Name(ourCertificate.subjectDN.name)
// This is a sanity check and should not fail unless things have been misconfigured
@ -423,9 +423,8 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
private fun getBridgeName(queueName: String, hostAndPort: HostAndPort): String = "$queueName -> $hostAndPort"
// This is called on one of Artemis' background threads
internal fun hostVerificationFail(peerLegalName: X500Name, expectedLegalName: X500Name) {
log.error("Peer has wrong CN - expected $expectedLegalName but got $peerLegalName. This is either a fatal " +
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!")
internal fun hostVerificationFail(expectedLegalName: X500Name, errorMsg: String?) {
log.error(errorMsg)
if (expectedLegalName == config.networkMapService?.legalName) {
// If the peer that failed host verification was the network map node then we're in big trouble and need to bail!
_networkMapConnectionFuture!!.setException(IOException("${config.networkMapService} failed host verification check"))
@ -466,7 +465,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
}
class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
override fun createConnector(configuration: MutableMap<String, Any>?,
override fun createConnector(configuration: MutableMap<String, Any>,
handler: BufferHandler?,
listener: ClientConnectionLifeCycleListener?,
closeExecutor: Executor?,
@ -478,7 +477,7 @@ class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
}
}
private class VerifyingNettyConnector(configuration: MutableMap<String, Any>?,
private class VerifyingNettyConnector(configuration: MutableMap<String, Any>,
handler: BufferHandler?,
listener: ClientConnectionLifeCycleListener?,
closeExecutor: Executor?,
@ -486,27 +485,37 @@ private class VerifyingNettyConnector(configuration: MutableMap<String, Any>?,
scheduledThreadPool: ScheduledExecutorService?,
protocolManager: ClientProtocolManager?) :
NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) {
private val server = configuration?.get(ArtemisMessagingServer::class.java.name) as? ArtemisMessagingServer
private val expecteLegalName: X500Name? = configuration?.get(ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME) as X500Name?
private val server = configuration[ArtemisMessagingServer::class.java.name] as ArtemisMessagingServer
private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration)
override fun createConnection(): Connection? {
val connection = super.createConnection() as NettyConnection?
if (connection != null && expecteLegalName != null) {
val peerLegalName: X500Name = connection
.channel
.pipeline()
.get(SslHandler::class.java)
.engine()
.session
.peerPrincipal
.name
.let(::X500Name)
if (peerLegalName != expecteLegalName) {
val connection = super.createConnection() as? NettyConnection
if (sslEnabled && connection != null) {
val expectedLegalName = configuration[ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME] as X500Name
try {
val session = connection.channel
.pipeline()
.get(SslHandler::class.java)
.engine()
.session
// Checks the peer name is the one we are expecting.
val peerLegalName = session.peerPrincipal.name.let(::X500Name)
require(peerLegalName == expectedLegalName) {
"Peer has wrong CN - expected $expectedLegalName but got $peerLegalName. This is either a fatal " +
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
}
// Make sure certificate has the same name.
val peerCertificate = X509CertificateHolder(session.peerCertificateChain.first().encoded)
require(peerCertificate.subject == expectedLegalName) {
"Peer has wrong subject name in the certificate - expected $expectedLegalName but got ${peerCertificate.subject}. This is either a fatal " +
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
}
X509Utilities.validateCertificateChain(X509CertImpl(session.localCertificates.last().encoded), *session.peerCertificates)
server.onTcpConnection(peerLegalName)
} catch (e: IllegalArgumentException) {
connection.close()
server!!.hostVerificationFail(peerLegalName, expecteLegalName)
return null // Artemis will keep trying to reconnect until it's told otherwise
} else {
server!!.onTcpConnection(peerLegalName)
server.hostVerificationFail(expectedLegalName, e.message)
return null
}
}
return connection
@ -547,7 +556,7 @@ sealed class CertificateChainCheckPolicy {
object LeafMustMatch : CertificateChainCheckPolicy() {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
val ourPublicKey = keyStore.getCertificate(CORDA_CLIENT_CA).publicKey
val ourPublicKey = keyStore.getCertificate(CORDA_CLIENT_TLS).publicKey
return object : Check {
override fun checkCertificateChain(theirChain: Array<X509Certificate>) {
val theirLeaf = theirChain.first().publicKey

View File

@ -14,6 +14,7 @@ import net.corda.core.utilities.unwrap
import net.corda.flows.NotaryException
import net.corda.node.services.api.ServiceHubInternal
import org.jetbrains.exposed.sql.Database
import java.nio.file.Path
import kotlin.concurrent.thread
/**
@ -21,14 +22,18 @@ import kotlin.concurrent.thread
*
* A transaction is notarised when the consensus is reached by the cluster on its uniqueness, and timestamp validity.
*/
class BFTNonValidatingNotaryService(services: ServiceHubInternal,
class BFTNonValidatingNotaryService(config: BFTSMaRtConfig,
services: ServiceHubInternal,
timestampChecker: TimestampChecker,
serverId: Int,
db: Database,
val client: BFTSMaRt.Client) : NotaryService {
private val client: BFTSMaRt.Client) : NotaryService {
init {
val configHandle = config.handle()
thread(name = "BFTSmartServer-$serverId", isDaemon = true) {
Server(serverId, db, "bft_smart_notary_committed_states", services, timestampChecker)
configHandle.use {
Server(configHandle.path, serverId, db, "bft_smart_notary_committed_states", services, timestampChecker)
}
}
}
@ -62,11 +67,12 @@ class BFTNonValidatingNotaryService(services: ServiceHubInternal,
}
}
private class Server(id: Int,
private class Server(configHome: Path,
id: Int,
db: Database,
tableName: String,
services: ServiceHubInternal,
timestampChecker: TimestampChecker) : BFTSMaRt.Server(id, db, tableName, services, timestampChecker) {
timestampChecker: TimestampChecker) : BFTSMaRt.Server(configHome, id, db, tableName, services, timestampChecker) {
override fun executeCommand(command: ByteArray): ByteArray {
val request = command.deserialize<BFTSMaRt.CommitRequest>()

View File

@ -32,6 +32,7 @@ import net.corda.node.services.transactions.BFTSMaRt.Server
import net.corda.node.utilities.JDBCHashMap
import net.corda.node.utilities.transaction
import org.jetbrains.exposed.sql.Database
import java.nio.file.Path
import java.util.*
/**
@ -66,13 +67,17 @@ object BFTSMaRt {
data class Signatures(val txSignatures: List<DigitalSignature>) : ClusterResponse()
}
class Client(val id: Int) : SingletonSerializeAsToken() {
class Client(config: BFTSMaRtConfig, private val clientId: Int) : SingletonSerializeAsToken() {
private val configHandle = config.handle()
companion object {
private val log = loggerFor<Client>()
}
/** A proxy for communicating with the BFT cluster */
private val proxy: ServiceProxy by lazy { buildProxy() }
private val proxy: ServiceProxy by lazy {
configHandle.use { buildProxy(it.path) }
}
/**
* Sends a transaction commit request to the BFT cluster. The [proxy] will deliver the request to every
@ -86,10 +91,10 @@ object BFTSMaRt {
return response
}
private fun buildProxy(): ServiceProxy {
private fun buildProxy(configHome: Path): ServiceProxy {
val comparator = buildResponseComparator()
val extractor = buildExtractor()
return ServiceProxy(id, "bft-smart-config", comparator, extractor)
return ServiceProxy(clientId, configHome.toString(), comparator, extractor)
}
/** A comparator to check if replies from two replicas are the same. */
@ -111,7 +116,7 @@ object BFTSMaRt {
val accepted = responses.filterIsInstance<ReplicaResponse.Signature>()
val rejected = responses.filterIsInstance<ReplicaResponse.Error>()
log.debug { "BFT Client $id: number of replicas accepted the commit: ${accepted.size}, rejected: ${rejected.size}" }
log.debug { "BFT Client $clientId: number of replicas accepted the commit: ${accepted.size}, rejected: ${rejected.size}" }
// TODO: only return an aggregate if the majority of signatures are replies
// TODO: return an error reported by the majority and not just the first one
@ -137,7 +142,8 @@ object BFTSMaRt {
* The validation logic can be specified by implementing the [executeCommand] method.
*/
@Suppress("LeakingThis")
abstract class Server(val id: Int,
abstract class Server(configHome: Path,
val replicaId: Int,
val db: Database,
tableName: String,
val services: ServiceHubInternal,
@ -152,7 +158,7 @@ object BFTSMaRt {
init {
// TODO: Looks like this statement is blocking. Investigate the bft-smart node startup.
ServiceReplica(id, "bft-smart-config", this, this, null, DefaultReplier())
ServiceReplica(replicaId, configHome.toString(), this, this, null, DefaultReplier())
}
override fun appExecuteUnordered(command: ByteArray, msgCtx: MessageContext): ByteArray? {

View File

@ -0,0 +1,61 @@
package net.corda.node.services.transactions
import com.google.common.net.HostAndPort
import net.corda.core.div
import java.io.FileWriter
import java.io.PrintWriter
import java.net.InetAddress
import java.nio.file.Files
/**
* BFT SMaRt can only be configured via files in a configHome directory.
* Each instance of this class creates such a configHome, accessible via [path].
* The files are deleted on [close] typically via [use], see [PathManager] for details.
*/
class BFTSMaRtConfig(replicaAddresses: List<HostAndPort>) : PathManager(Files.createTempDirectory("bft-smart-config")) {
companion object {
internal val portIsClaimedFormat = "Port %s is claimed by another replica: %s"
}
init {
val claimedPorts = mutableSetOf<Int>()
replicaAddresses.map { it.port }.forEach { base ->
// Each replica claims the configured port and the next one:
(0..1).map { base + it }.forEach { port ->
claimedPorts.add(port) || throw IllegalArgumentException(portIsClaimedFormat.format(port, claimedPorts))
}
}
configWriter("hosts.config") {
replicaAddresses.forEachIndexed { index, address ->
// The documentation strongly recommends IP addresses:
println("${index} ${InetAddress.getByName(address.host).hostAddress} ${address.port}")
}
}
val n = replicaAddresses.size
val systemConfig = String.format(javaClass.getResource("system.config.printf").readText(), n, maxFaultyReplicas(n))
configWriter("system.config") {
print(systemConfig)
}
}
private fun configWriter(name: String, block: PrintWriter.() -> Unit) {
// Default charset, consistent with loaders:
FileWriter((path / name).toFile()).use {
PrintWriter(it).use {
it.run(block)
}
}
}
}
fun maxFaultyReplicas(clusterSize: Int) = (clusterSize - 1) / 3
fun minCorrectReplicas(clusterSize: Int) = (2 * clusterSize + 3) / 3
fun minClusterSize(maxFaultyReplicas: Int) = maxFaultyReplicas * 3 + 1
fun bftSMaRtSerialFilter(clazz: Class<*>): Boolean = clazz.name.let {
it.startsWith("bftsmart.")
|| it.startsWith("java.security.")
|| it.startsWith("java.util.")
|| it.startsWith("java.lang.")
|| it.startsWith("java.net.")
}

View File

@ -0,0 +1,43 @@
package net.corda.node.services.transactions
import net.corda.core.internal.addShutdownHook
import java.io.Closeable
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicInteger
internal class DeleteOnExitPath(internal val path: Path) {
private val shutdownHook = addShutdownHook { dispose() }
internal fun dispose() {
path.toFile().deleteRecursively()
shutdownHook.cancel()
}
}
open class PathHandle internal constructor(private val deleteOnExitPath: DeleteOnExitPath, private val handleCounter: AtomicInteger) : Closeable {
val path
get(): Path {
val path = deleteOnExitPath.path
check(handleCounter.get() != 0) { "Defunct path: $path" }
return path
}
init {
handleCounter.incrementAndGet()
}
fun handle() = PathHandle(deleteOnExitPath, handleCounter)
override fun close() {
if (handleCounter.decrementAndGet() == 0) {
deleteOnExitPath.dispose()
}
}
}
/**
* An instance of this class is a handle on a temporary [path].
* If necessary, additional handles on the same path can be created using the [handle] method.
* The path is (recursively) deleted when [close] is called on the last handle, typically at the end of a [use] expression.
* The value of eager cleanup of temporary files is that there are cases when shutdown hooks don't run e.g. SIGKILL.
*/
open class PathManager(path: Path) : PathHandle(DeleteOnExitPath(path), AtomicInteger())

View File

@ -127,7 +127,7 @@ class RaftUniquenessProvider(
return NettyTransport.builder()
.withSsl()
.withSslProtocol(SslProtocol.TLSv1_2)
.withKeyStorePath(config.keyStoreFile.toString())
.withKeyStorePath(config.sslKeystore.toString())
.withKeyStorePassword(config.keyStorePassword)
.withTrustStorePath(config.trustStoreFile.toString())
.withTrustStorePassword(config.trustStorePassword)

View File

@ -3,6 +3,7 @@ package net.corda.node.utilities.registration
import net.corda.core.*
import net.corda.core.crypto.*
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.node.services.config.NodeConfiguration
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
@ -31,15 +32,16 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
fun buildKeystore() {
config.certificatesDirectory.createDirectories()
val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword)
val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.nodeKeystore, keystorePassword)
if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) {
// Create or load self signed keypair from the key store.
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
if (!caKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) {
val selfSignCert = X509Utilities.createSelfSignedCACert(config.myLegalName)
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair)
// Save to the key store.
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, selfSignCert.keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate))
caKeyStore.save(config.keyStoreFile, keystorePassword)
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert))
caKeyStore.save(config.nodeKeystore, keystorePassword)
}
val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
val requestId = submitOrResumeCertificateSigningRequest(keyPair)
@ -58,13 +60,22 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
// Save private key and certificate chain to the key store.
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
caKeyStore.save(config.keyStoreFile, keystorePassword)
caKeyStore.save(config.nodeKeystore, keystorePassword)
// Save root certificates to trust store.
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
// Assumes certificate chain always starts with client certificate and end with root certificate.
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last())
trustStore.save(config.trustStoreFile, config.trustStorePassword)
println("Certificate and private key stored in ${config.keyStoreFile}.")
println("Node private key and certificate stored in ${config.nodeKeystore}.")
println("Generating SSL certificate for node messaging service.")
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA)
val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, caCert.subject, sslKey.public)
val sslKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.sslKeystore, keystorePassword)
sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates))
sslKeyStore.save(config.sslKeystore, config.keyStorePassword)
println("SSL private key and certificate stored in ${config.sslKeystore}.")
// All done, clean up temp files.
requestIdStore.deleteIfExists()
} else {
@ -72,6 +83,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
}
}
/**
* Poll Certificate Signing Server for approved certificate,
* enter a slow polling loop if server return null.

View File

@ -32,10 +32,10 @@ system.communication.defaultkeys = true
############################################
#Number of servers in the group
system.servers.num = 4
system.servers.num = %s
#Maximum number of faulty replicas
system.servers.f = 1
system.servers.f = %s
#Timeout to asking for a client request
system.totalordermulticast.timeout = 2000

View File

@ -0,0 +1,31 @@
package net.corda.node
import org.junit.Test
import java.io.IOException
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.test.fail
class SerialFilterTests {
@Test
fun `null and primitives are accepted and arrays are unwrapped`() {
val acceptClass = { _: Class<*> -> fail("Should not be invoked.") }
listOf(null, Byte::class.javaPrimitiveType, IntArray::class.java, Array<CharArray>::class.java).forEach {
assertTrue(SerialFilter.applyPredicate(acceptClass, it))
}
}
@Test
fun `the predicate is applied to the componentType`() {
val classes = mutableListOf<Class<*>>()
val acceptClass = { clazz: Class<*> ->
classes.add(clazz)
false
}
listOf(String::class.java, Array<Unit>::class.java, Array<Array<IOException>>::class.java).forEach {
assertFalse(SerialFilter.applyPredicate(acceptClass, it))
}
assertEquals(listOf<Class<*>>(String::class.java, Unit::class.java, IOException::class.java), classes)
}
}

View File

@ -5,6 +5,7 @@ import net.corda.core.crypto.commonName
import net.corda.core.div
import net.corda.core.getOrThrow
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.WHITESPACE
import net.corda.testing.node.NodeBasedTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
@ -12,11 +13,8 @@ import org.junit.Test
class NodeTest : NodeBasedTest() {
@Test
fun `empty plugins directory`() {
val baseDirectory = tempFolder.root.toPath() / ALICE.name.commonName
val baseDirectory = baseDirectory(ALICE.name)
(baseDirectory / "plugins").createDirectories()
val node = startNode(ALICE.name).getOrThrow()
// Make sure we created the plugins dir in the correct place
assertThat(baseDirectory).isEqualTo(node.configuration.baseDirectory)
startNode(ALICE.name).getOrThrow()
}
}
}

View File

@ -1,7 +1,6 @@
package net.corda.node.services.network
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.services.IdentityService
@ -66,13 +65,13 @@ class InMemoryIdentityServiceTests {
*/
@Test
fun `assert unknown anonymous key is unrecognised`() {
val rootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name)
val txCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, rootCertAndKey)
val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey)
val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val service = InMemoryIdentityService()
val rootKey = rootCertAndKey.keyPair
// TODO: Generate certificate with an EdDSA key rather than ECDSA
val identity = Party(rootCertAndKey)
val txIdentity = AnonymousParty(txCertAndKey.keyPair.public)
val identity = Party(CertificateAndKeyPair(rootCert, rootKey))
val txIdentity = AnonymousParty(txKey.public)
assertFailsWith<IdentityService.UnknownAnonymousPartyException> {
service.assertOwnership(identity, txIdentity)
@ -85,20 +84,26 @@ class InMemoryIdentityServiceTests {
*/
@Test
fun `assert ownership`() {
val aliceRootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name)
val aliceTxCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, aliceRootCertAndKey)
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCertAndKey, aliceTxCertAndKey.certificate, false).certPath
val bobRootCertAndKey = X509Utilities.createSelfSignedCACert(BOB.name)
val bobTxCertAndKey = X509Utilities.createIntermediateCert(BOB.name, bobRootCertAndKey)
val bobCertPath = X509Utilities.createCertificatePath(bobRootCertAndKey, bobTxCertAndKey.certificate, false).certPath
val service = InMemoryIdentityService()
val alice = Party(aliceRootCertAndKey)
val anonymousAlice = AnonymousParty(aliceTxCertAndKey.keyPair.public)
val bob = Party(bobRootCertAndKey)
val anonymousBob = AnonymousParty(bobTxCertAndKey.keyPair.public)
val aliceRootKey = Crypto.generateKeyPair()
val aliceRootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, aliceRootKey)
val aliceTxKey = Crypto.generateKeyPair()
val aliceTxCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, aliceRootCert, aliceRootKey, ALICE.name, aliceTxKey.public)
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCert, aliceTxCert, revocationEnabled = false)
service.registerPath(aliceRootCertAndKey.certificate, anonymousAlice, aliceCertPath)
service.registerPath(bobRootCertAndKey.certificate, anonymousBob, bobCertPath)
val bobRootKey = Crypto.generateKeyPair()
val bobRootCert = X509Utilities.createSelfSignedCACertificate(BOB.name, bobRootKey)
val bobTxKey = Crypto.generateKeyPair()
val bobTxCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, bobRootCert, bobRootKey, BOB.name, bobTxKey.public)
val bobCertPath = X509Utilities.createCertificatePath(bobRootCert, bobTxCert, revocationEnabled = false)
val service = InMemoryIdentityService()
val alice = Party(CertificateAndKeyPair(aliceRootCert, aliceRootKey))
val anonymousAlice = AnonymousParty(aliceTxKey.public)
val bob = Party(CertificateAndKeyPair(bobRootCert, bobRootKey))
val anonymousBob = AnonymousParty(bobTxKey.public)
service.registerPath(aliceRootCert, anonymousAlice, aliceCertPath)
service.registerPath(bobRootCert, anonymousBob, bobCertPath)
// Verify that paths are verified
service.assertOwnership(alice, anonymousAlice)

View File

@ -0,0 +1,40 @@
package net.corda.node.services.transactions
import com.google.common.net.HostAndPort
import net.corda.node.services.transactions.BFTSMaRtConfig.Companion.portIsClaimedFormat
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class BFTSMaRtConfigTests {
@Test
fun `replica arithmetic`() {
(1..20).forEach { n ->
assertEquals(n, maxFaultyReplicas(n) + minCorrectReplicas(n))
}
(1..3).forEach { n -> assertEquals(0, maxFaultyReplicas(n)) }
(4..6).forEach { n -> assertEquals(1, maxFaultyReplicas(n)) }
(7..9).forEach { n -> assertEquals(2, maxFaultyReplicas(n)) }
10.let { n -> assertEquals(3, maxFaultyReplicas(n)) }
}
@Test
fun `min cluster size`() {
assertEquals(1, minClusterSize(0))
assertEquals(4, minClusterSize(1))
assertEquals(7, minClusterSize(2))
assertEquals(10, minClusterSize(3))
}
@Test
fun `overlapping port ranges are rejected`() {
fun addresses(vararg ports: Int) = ports.map { HostAndPort.fromParts("localhost", it) }
assertFailsWith(IllegalArgumentException::class, portIsClaimedFormat.format(11001, setOf(11000, 11001))) {
BFTSMaRtConfig(addresses(11000, 11001)).use {}
}
assertFailsWith(IllegalArgumentException::class, portIsClaimedFormat.format(11001, setOf(11001, 11002))) {
BFTSMaRtConfig(addresses(11001, 11000)).use {}
}
BFTSMaRtConfig(addresses(11000, 11002)).use {} // Non-overlapping.
}
}

View File

@ -0,0 +1,32 @@
package net.corda.node.services.transactions
import net.corda.core.exists
import org.junit.Test
import java.nio.file.Files
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class PathManagerTests {
@Test
fun `path deleted when manager closed`() {
val manager = PathManager(Files.createTempFile(javaClass.simpleName, null))
val leakedPath = manager.use {
it.path.also { assertTrue(it.exists()) }
}
assertFalse(leakedPath.exists())
assertFailsWith(IllegalStateException::class) { manager.path }
}
@Test
fun `path deleted when handle closed`() {
val handle = PathManager(Files.createTempFile(javaClass.simpleName, null)).use {
it.handle()
}
val leakedPath = handle.use {
it.path.also { assertTrue(it.exists()) }
}
assertFalse(leakedPath.exists())
assertFailsWith(IllegalStateException::class) { handle.path }
}
}

View File

@ -3,14 +3,12 @@ package net.corda.node.utilities.registration
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.mock
import net.corda.core.crypto.KeyStoreUtilities
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.*
import net.corda.core.exists
import net.corda.core.utilities.ALICE
import net.corda.testing.TestNodeConfiguration
import net.corda.testing.getTestX509Name
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@ -31,7 +29,7 @@ class NetworkRegistrationHelperTest {
"CORDA_INTERMEDIATE_CA",
"CORDA_ROOT_CA")
.map { getTestX509Name(it) }
val certs = identities.map { X509Utilities.createSelfSignedCACert(it).certificate }
val certs = identities.map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
.toTypedArray()
val certService: NetworkRegistrationService = mock {
@ -44,32 +42,45 @@ class NetworkRegistrationHelperTest {
myLegalName = ALICE.name,
networkMapService = null)
assertFalse(config.keyStoreFile.exists())
assertFalse(config.nodeKeystore.exists())
assertFalse(config.sslKeystore.exists())
assertFalse(config.trustStoreFile.exists())
NetworkRegistrationHelper(config, certService).buildKeystore()
assertTrue(config.keyStoreFile.exists())
assertTrue(config.nodeKeystore.exists())
assertTrue(config.sslKeystore.exists())
assertTrue(config.trustStoreFile.exists())
KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword).run {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
assertEquals(3, certificateChain.size)
val nodeKeystore = KeyStoreUtilities.loadKeyStore(config.nodeKeystore, config.keyStorePassword)
val sslKeystore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword)
val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
nodeKeystore.run {
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY))
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY))
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
assertEquals(3, certificateChain.size)
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { X509CertificateHolder(it.encoded).subject.commonName })
}
KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword).run {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
sslKeystore.run {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)
assertEquals(4, certificateChain.size)
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { X509CertificateHolder(it.encoded).subject.commonName })
}
trustStore.run {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY))
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY))
}
}
}

View File

@ -7,5 +7,5 @@ Please refer to `README.md` in the individual project folders. There are the fo
* **trader-demo** A simple driver for exercising the two party trading flow. In this scenario, a buyer wants to purchase some commercial paper by swapping his cash for commercial paper. The seller learns that the buyer exists, and sends them a message to kick off the trade. The seller, having obtained his CP, then quits and the buyer goes back to waiting. The buyer will sell as much CP as he can! **We recommend starting with this demo.**
* **Network-visualiser** A tool that uses a simulation to visualise the interaction and messages between nodes on the Corda network. Currently only works for the IRS demo.
* **simm-valudation-demo** A demo showing two nodes reaching agreement on the valuation of a derivatives portfolio.
* **raft-notary-demo** A simple demonstration of a node getting multiple transactions notarised by a distributed (Raft-based) notary.
* **bank-of-corda-demo** A demo showing a node acting as an issuer of fungible assets (initially Cash)
* **notary-demo** A simple demonstration of a node getting multiple transactions notarised by a single or distributed (Raft or BFT SMaRt) notary.
* **bank-of-corda-demo** A demo showing a node acting as an issuer of fungible assets (initially Cash)

View File

@ -1,5 +1,5 @@
# Distributed Notary (Raft) Demo
# Distributed Notary Demo
This program is a simple demonstration of a node getting multiple transactions notarised by a distributed (Raft-based) notary.
This program is a simple demonstration of a node getting multiple transactions notarised by a distributed (Raft or BFT SMaRt) notary.
Please see docs/build/html/running-the-demos.html to learn how to use this demo.

View File

@ -41,7 +41,7 @@ publishing {
publications {
jarAndSources(MavenPublication) {
from components.java
artifactId 'raftnotarydemo'
artifactId 'notarydemo'
artifact sourceJar
artifact javadocJar
@ -57,6 +57,10 @@ task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
definitionClass = 'net.corda.notarydemo.RaftNotaryCordform'
}
task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
definitionClass = 'net.corda.notarydemo.BFTNotaryCordform'
}
task notarise(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'net.corda.notarydemo.NotariseKt'

View File

@ -6,8 +6,6 @@ import net.corda.node.driver.driver
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
fun CordformDefinition.node(configure: CordformNode.() -> Unit) = addNode { cordformNode -> cordformNode.configure() }
fun CordformDefinition.clean() {
System.err.println("Deleting: $driverDirectory")
driverDirectory.toFile().deleteRecursively()

View File

@ -0,0 +1,26 @@
package net.corda.demorun.util
import com.google.common.net.HostAndPort
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
import net.corda.core.node.services.ServiceInfo
import net.corda.nodeapi.User
import org.bouncycastle.asn1.x500.X500Name
fun CordformDefinition.node(configure: CordformNode.() -> Unit) {
addNode { cordformNode -> cordformNode.configure() }
}
fun CordformNode.name(name: X500Name) = name(name.toString())
fun CordformNode.rpcUsers(vararg users: User) {
rpcUsers = users.map { it.toMap() }
}
fun CordformNode.advertisedServices(vararg services: ServiceInfo) {
advertisedServices = services.map { it.toString() }
}
fun CordformNode.notaryClusterAddresses(vararg addresses: HostAndPort) {
notaryClusterAddresses = addresses.map { it.toString() }
}

View File

@ -0,0 +1,69 @@
package net.corda.notarydemo
import com.google.common.net.HostAndPort
import net.corda.core.div
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.demorun.util.*
import net.corda.demorun.runNodes
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformContext
import net.corda.cordform.CordformNode
import net.corda.core.mapToArray
import net.corda.node.services.transactions.minCorrectReplicas
import org.bouncycastle.asn1.x500.X500Name
fun main(args: Array<String>) = BFTNotaryCordform.runNodes()
private val clusterSize = 4 // Minimum size thats tolerates a faulty replica.
private val notaryNames = createNotaryNames(clusterSize)
object BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", notaryNames[0]) {
private val clusterName = X500Name("CN=BFT,O=R3,OU=corda,L=Zurich,C=CH")
private val advertisedService = ServiceInfo(BFTNonValidatingNotaryService.type, clusterName)
init {
node {
name(ALICE.name)
p2pPort(10002)
rpcPort(10003)
rpcUsers(notaryDemoUser)
}
node {
name(BOB.name)
p2pPort(10005)
rpcPort(10006)
}
val clusterAddresses = (0 until clusterSize).mapToArray { HostAndPort.fromParts("localhost", 11000 + it * 10) }
fun notaryNode(replicaId: Int, configure: CordformNode.() -> Unit) = node {
name(notaryNames[replicaId])
advertisedServices(advertisedService)
notaryClusterAddresses(*clusterAddresses)
bftReplicaId(replicaId)
configure()
}
notaryNode(0) {
p2pPort(10009)
rpcPort(10010)
}
notaryNode(1) {
p2pPort(10013)
rpcPort(10014)
}
notaryNode(2) {
p2pPort(10017)
rpcPort(10018)
}
notaryNode(3) {
p2pPort(10021)
rpcPort(10022)
}
}
override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, advertisedService.type.id, clusterName, minCorrectReplicas(clusterSize))
}
}

View File

@ -3,7 +3,7 @@ package net.corda.notarydemo
import net.corda.demorun.clean
fun main(args: Array<String>) {
listOf(SingleNotaryCordform, RaftNotaryCordform).forEach {
listOf(SingleNotaryCordform, RaftNotaryCordform, BFTNotaryCordform).forEach {
it.clean()
}
}

View File

@ -2,10 +2,12 @@ package net.corda.notarydemo
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.notUsed
import net.corda.core.crypto.toStringShort
import net.corda.core.getOrThrow
import net.corda.core.map
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.transactions.SignedTransaction
@ -14,11 +16,10 @@ import net.corda.notarydemo.flows.DummyIssueAndMove
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
fun main(args: Array<String>) {
val host = HostAndPort.fromString("localhost:10003")
println("Connecting to the recipient node ($host)")
CordaRPCClient(host).start("demo", "demo").use {
val api = NotaryDemoClientApi(it.proxy)
api.startNotarisation()
val address = HostAndPort.fromParts("localhost", 10003)
println("Connecting to the recipient node ($address)")
CordaRPCClient(address).start(notaryDemoUser.username, notaryDemoUser.password).use {
NotaryDemoClientApi(it.proxy).notarise(10)
}
}
@ -27,34 +28,23 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
private val notary by lazy {
val (parties, partyUpdates) = rpc.networkMapUpdates()
partyUpdates.notUsed()
parties.first { it.advertisedServices.any { it.info.type.isNotary() } }.notaryIdentity
parties.filter { it.advertisedServices.any { it.info.type.isNotary() } }.map { it.notaryIdentity }.distinct().single()
}
private val counterpartyNode by lazy {
val (parties, partyUpdates) = rpc.networkMapUpdates()
partyUpdates.notUsed()
parties.first { it.legalIdentity.name == BOB.name }
}
private companion object {
private val TRANSACTION_COUNT = 10
parties.single { it.legalIdentity.name == BOB.name }
}
/** Makes calls to the node rpc to start transaction notarisation. */
fun startNotarisation() {
notarise(TRANSACTION_COUNT)
}
fun notarise(count: Int) {
println("Notary: \"${notary.name}\", with composite key: ${notary.owningKey.toStringShort()}")
val transactions = buildTransactions(count)
val signers = notariseTransactions(transactions)
val transactionSigners = transactions.zip(signers).map {
val (tx, signer) = it
"Tx [${tx.tx.id.prefixChars()}..] signed by $signer"
}.joinToString("\n")
println("Notary: \"${notary.name}\", with composite key: ${notary.owningKey.toStringShort()}\n" +
"Notarised ${transactions.size} transactions:\n" + transactionSigners)
println("Notarised ${transactions.size} transactions:")
transactions.zip(notariseTransactions(transactions)).forEach { (tx, signersFuture) ->
println("Tx [${tx.tx.id.prefixChars()}..] signed by ${signersFuture.getOrThrow().joinToString()}")
}
}
/**
@ -63,10 +53,9 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
* as it consumes the original asset and creates a copy with the new owner as its output.
*/
private fun buildTransactions(count: Int): List<SignedTransaction> {
val moveTransactions = (1..count).map {
return Futures.allAsList((1..count).map {
rpc.startFlow(::DummyIssueAndMove, notary, counterpartyNode.legalIdentity).returnValue
}
return Futures.allAsList(moveTransactions).getOrThrow()
}).getOrThrow()
}
/**
@ -75,10 +64,9 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
*
* @return a list of encoded signer public keys - one for every transaction
*/
private fun notariseTransactions(transactions: List<SignedTransaction>): List<String> {
// TODO: Remove this suppress when we upgrade to kotlin 1.1 or when JetBrain fixes the bug.
@Suppress("UNSUPPORTED_FEATURE")
val signatureFutures = transactions.map { rpc.startFlow(::RPCStartableNotaryFlowClient, it).returnValue }
return Futures.allAsList(signatureFutures).getOrThrow().map { it.map { it.by.toStringShort() }.joinToString() }
private fun notariseTransactions(transactions: List<SignedTransaction>): List<ListenableFuture<List<String>>> {
return transactions.map {
rpc.startFlow(::RPCStartableNotaryFlowClient, it).returnValue.map { it.map { it.by.toStringShort() } }
}
}
}

View File

@ -0,0 +1,70 @@
package net.corda.notarydemo
import com.google.common.net.HostAndPort
import net.corda.core.crypto.appendToCommonName
import net.corda.core.div
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demorun.util.*
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformContext
import net.corda.cordform.CordformNode
import net.corda.demorun.runNodes
import net.corda.demorun.util.node
import org.bouncycastle.asn1.x500.X500Name
fun main(args: Array<String>) = RaftNotaryCordform.runNodes()
internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
private val notaryNames = createNotaryNames(3)
object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", notaryNames[0]) {
private val clusterName = X500Name("CN=Raft,O=R3,OU=corda,L=Zurich,C=CH")
private val advertisedService = ServiceInfo(RaftValidatingNotaryService.type, clusterName)
init {
node {
name(ALICE.name)
p2pPort(10002)
rpcPort(10003)
rpcUsers(notaryDemoUser)
}
node {
name(BOB.name)
p2pPort(10005)
rpcPort(10006)
}
fun notaryNode(index: Int, configure: CordformNode.() -> Unit) = node {
name(notaryNames[index])
advertisedServices(advertisedService)
configure()
}
notaryNode(0) {
notaryNodePort(10008)
p2pPort(10009)
rpcPort(10010)
}
val clusterAddress = HostAndPort.fromParts("localhost", 10008) // Otherwise each notary forms its own cluster.
notaryNode(1) {
notaryNodePort(10012)
p2pPort(10013)
rpcPort(10014)
notaryClusterAddresses(clusterAddress)
}
notaryNode(2) {
notaryNodePort(10016)
p2pPort(10017)
rpcPort(10018)
notaryClusterAddresses(clusterAddress)
}
}
override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, advertisedService.type.id, clusterName)
}
}

View File

@ -5,7 +5,6 @@ import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demorun.node
import net.corda.demorun.runNodes
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService
@ -14,31 +13,30 @@ import net.corda.notarydemo.flows.DummyIssueAndMove
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformContext
import net.corda.demorun.util.*
fun main(args: Array<String>) = SingleNotaryCordform.runNodes()
val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>()))
object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", DUMMY_NOTARY.name) {
init {
node {
name(ALICE.name.toString())
nearestCity("London")
name(ALICE.name)
p2pPort(10002)
rpcPort(10003)
rpcUsers = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())).toMap())
rpcUsers(notaryDemoUser)
}
node {
name(BOB.name.toString())
nearestCity("New York")
name(BOB.name)
p2pPort(10005)
rpcPort(10006)
}
node {
name(DUMMY_NOTARY.name.toString())
nearestCity("London")
advertisedServices = listOf(ServiceInfo(ValidatingNotaryService.type).toString())
name(DUMMY_NOTARY.name)
p2pPort(10009)
rpcPort(10010)
notaryNodePort(10008)
advertisedServices(ServiceInfo(ValidatingNotaryService.type))
}
}

View File

@ -1,73 +0,0 @@
package net.corda.notarydemo
import net.corda.core.crypto.appendToCommonName
import net.corda.core.div
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demorun.node
import net.corda.demorun.runNodes
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.nodeapi.User
import net.corda.notarydemo.flows.DummyIssueAndMove
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformContext
import org.bouncycastle.asn1.x500.X500Name
fun main(args: Array<String>) = RaftNotaryCordform.runNodes()
private val notaryNames = (1..3).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", notaryNames[0]) {
private val advertisedNotary = ServiceInfo(RaftValidatingNotaryService.type, X500Name("CN=Raft,O=R3,OU=corda,L=Zurich,C=CH"))
init {
node {
name(ALICE.name.toString())
nearestCity("London")
p2pPort(10002)
rpcPort(10003)
rpcUsers = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())).toMap())
}
node {
name(BOB.name.toString())
nearestCity("New York")
p2pPort(10005)
rpcPort(10006)
}
node {
name(notaryNames[0].toString())
nearestCity("London")
advertisedServices = listOf(advertisedNotary.toString())
p2pPort(10009)
rpcPort(10010)
notaryNodePort(10008)
}
node {
name(notaryNames[1].toString())
nearestCity("London")
advertisedServices = listOf(advertisedNotary.toString())
p2pPort(10013)
rpcPort(10014)
notaryNodePort(10012)
notaryClusterAddresses = listOf("localhost:10008")
}
node {
name(notaryNames[2].toString())
nearestCity("London")
advertisedServices = listOf(advertisedNotary.toString())
p2pPort(10017)
rpcPort(10018)
notaryNodePort(10016)
notaryClusterAddresses = listOf("localhost:10008")
}
}
override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, advertisedNotary.type.id, advertisedNotary.name!!)
}
}

View File

@ -30,7 +30,7 @@ include 'samples:trader-demo'
include 'samples:irs-demo'
include 'samples:network-visualiser'
include 'samples:simm-valuation-demo'
include 'samples:raft-notary-demo'
include 'samples:notary-demo'
include 'samples:bank-of-corda-demo'
include 'cordform-common'
include 'doorman'

View File

@ -189,7 +189,7 @@ fun testConfiguration(baseDirectory: Path, legalName: X500Name, basePort: Int):
rpcAddress = HostAndPort.fromParts("localhost", basePort + 1),
messagingServerAddress = null,
extraAdvertisedServiceIds = emptyList(),
notaryNodeId = null,
bftReplicaId = null,
notaryNodeAddress = null,
notaryClusterAddresses = emptyList(),
certificateChainCheckPolicies = emptyList(),
@ -217,7 +217,6 @@ fun getTestX509Name(commonName: String): X500Name {
val nameBuilder = X500NameBuilder(BCStyle.INSTANCE)
nameBuilder.addRDN(BCStyle.CN, commonName)
nameBuilder.addRDN(BCStyle.O, "R3")
nameBuilder.addRDN(BCStyle.OU, "Corda QA Department")
nameBuilder.addRDN(BCStyle.L, "New York")
nameBuilder.addRDN(BCStyle.C, "US")
return nameBuilder.build()

View File

@ -1,7 +1,6 @@
package net.corda.testing.messaging
import com.google.common.net.HostAndPort
import net.corda.core.crypto.X509Utilities
import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.ConnectionDirection
@ -37,6 +36,10 @@ class SimpleMQClient(val target: HostAndPort,
fun createMessage(): ClientMessage = session.createMessage(false)
fun stop() {
sessionFactory.close()
try {
sessionFactory.close()
} catch (e: Exception) {
// sessionFactory might not have initialised.
}
}
}

View File

@ -4,10 +4,12 @@ import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.*
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.appendToCommonName
import net.corda.core.crypto.commonName
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.DUMMY_MAP
import net.corda.core.utilities.WHITESPACE
import net.corda.node.driver.addressMustNotBeBound
import net.corda.node.internal.Node
import net.corda.node.services.config.ConfigHelper
@ -107,7 +109,7 @@ abstract class NodeBasedTest {
clusterSize: Int,
serviceType: ServiceType = RaftValidatingNotaryService.type): ListenableFuture<List<Node>> {
ServiceIdentityGenerator.generateToDisk(
(0 until clusterSize).map { tempFolder.root.toPath() / "${notaryName.commonName}-$it" },
(0 until clusterSize).map { baseDirectory(notaryName.appendToCommonName("-$it")) },
serviceType.id,
notaryName)
@ -133,12 +135,14 @@ abstract class NodeBasedTest {
}
}
protected fun baseDirectory(legalName: X500Name) = tempFolder.root.toPath() / legalName.commonName.replace(WHITESPACE, "")
private fun startNodeInternal(legalName: X500Name,
platformVersion: Int,
advertisedServices: Set<ServiceInfo>,
rpcUsers: List<User>,
configOverrides: Map<String, Any>): Node {
val baseDirectory = (tempFolder.root.toPath() / legalName.commonName).createDirectories()
val baseDirectory = baseDirectory(legalName).createDirectories()
val localPort = getFreeLocalPorts("localhost", 2)
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory,

View File

@ -5,6 +5,7 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.core.ErrorOr
import net.corda.core.internal.addShutdownHook
import net.corda.core.div
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
@ -51,11 +52,11 @@ class Verifier {
val session = sessionFactory.createSession(
VerifierApi.VERIFIER_USERNAME, VerifierApi.VERIFIER_USERNAME, false, true, true, locator.isPreAcknowledge, locator.ackBatchSize
)
Runtime.getRuntime().addShutdownHook(Thread {
addShutdownHook {
log.info("Shutting down")
session.close()
sessionFactory.close()
})
}
val consumer = session.createConsumer(VERIFICATION_REQUESTS_QUEUE_NAME)
val replyProducer = session.createProducer()
consumer.setMessageHandler {

View File

@ -79,7 +79,7 @@ class NodeWebServer(val config: WebServerConfig) {
httpsConfiguration.outputBufferSize = 32768
httpsConfiguration.addCustomizer(SecureRequestCustomizer())
val sslContextFactory = SslContextFactory()
sslContextFactory.keyStorePath = config.keyStoreFile.toString()
sslContextFactory.keyStorePath = config.sslKeystore.toString()
sslContextFactory.setKeyStorePassword(config.keyStorePassword)
sslContextFactory.setKeyManagerPassword(config.keyStorePassword)
sslContextFactory.setTrustStorePath(config.trustStoreFile.toString())