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

6
.idea/compiler.xml generated
View File

@ -61,10 +61,10 @@
<module name="node_integrationTest" target="1.8" /> <module name="node_integrationTest" target="1.8" />
<module name="node_main" target="1.8" /> <module name="node_main" target="1.8" />
<module name="node_test" 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_main" target="1.8" />
<module name="quasar-hook_test" 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_integrationTest" target="1.8" />
<module name="rpc_main" target="1.8" /> <module name="rpc_main" target="1.8" />
<module name="rpc_smokeTest" target="1.8" /> <module name="rpc_smokeTest" target="1.8" />
@ -98,4 +98,4 @@
<component name="JavacSettings"> <component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" /> <option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component> </component>
</project> </project>

View File

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

View File

@ -25,7 +25,8 @@ public class CordformNode {
public List<String> advertisedServices = emptyList(); 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(); 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. * @param notaryPort The Raft port.
*/ */
public void notaryNodePort(Integer notaryPort) { public void notaryNodePort(Integer notaryPort) {
config = config.withValue("notaryNodeAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + 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>.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!!) } 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 { inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R) = mapToArray(transform, iterator(), size)
val iterator = iterator() 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 var expected = 0
Array(size) { Array(size) {
expected++ == it || throw UnsupportedOperationException("Array constructor is non-sequential!") 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.PKCSObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x500.X500Name 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.asn1.x9.X9ObjectIdentifiers
import org.bouncycastle.cert.bc.BcX509ExtensionUtils import org.bouncycastle.cert.bc.BcX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter 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. * 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, subject: X500Name, subjectPublicKey: PublicKey,
keyUsage: KeyUsage, purposes: List<KeyPurposeId>,
validityWindow: Pair<Date, Date>, validityWindow: Pair<Date, Date>,
pathLength: Int? = null, subjectAlternativeName: List<GeneralName>? = null): X509Certificate { nameConstraints: NameConstraints? = null): X509Certificate {
val signatureScheme = findSignatureScheme(issuerKeyPair.private) val signatureScheme = findSignatureScheme(issuerKeyPair.private)
val provider = providerMap[signatureScheme.providerName] val provider = providerMap[signatureScheme.providerName]
val serial = BigInteger.valueOf(random63BitValue()) 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) val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectPublicKey.encoded))) .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.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA))
.addExtension(Extension.keyUsage, false, keyUsage) .addExtension(Extension.keyUsage, false, certificateType.keyUsage)
.addExtension(Extension.extendedKeyUsage, false, keyPurposes) .addExtension(Extension.extendedKeyUsage, false, keyPurposes)
if (subjectAlternativeName != null && subjectAlternativeName.isNotEmpty()) { if (nameConstraints != null) {
builder.addExtension(Extension.subjectAlternativeName, false, DERSequence(subjectAlternativeName.toTypedArray())) builder.addExtension(Extension.nameConstraints, true, nameConstraints)
} }
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider) val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
return JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer)).apply { return JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer)).apply {
checkValidity(Date()) checkValidity(Date())

View File

@ -5,23 +5,19 @@ import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.asn1.x500.style.BCStyle
import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.*
import org.bouncycastle.asn1.x509.KeyPurposeId
import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.IPAddress
import org.bouncycastle.util.io.pem.PemReader import org.bouncycastle.util.io.pem.PemReader
import java.io.FileReader import java.io.FileReader
import java.io.FileWriter import java.io.FileWriter
import java.io.InputStream import java.io.InputStream
import java.net.InetAddress
import java.nio.file.Path import java.nio.file.Path
import java.security.InvalidAlgorithmParameterException
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore import java.security.KeyStore
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.* import java.security.cert.*
import java.security.cert.Certificate
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
@ -31,20 +27,12 @@ object X509Utilities {
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256 val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
// Aliases for private keys and certificates. // Aliases for private keys and certificates.
val CORDA_ROOT_CA_PRIVATE_KEY = "cordarootcaprivatekey"
val CORDA_ROOT_CA = "cordarootca" val CORDA_ROOT_CA = "cordarootca"
val CORDA_INTERMEDIATE_CA_PRIVATE_KEY = "cordaintermediatecaprivatekey"
val CORDA_INTERMEDIATE_CA = "cordaintermediateca" val CORDA_INTERMEDIATE_CA = "cordaintermediateca"
val CORDA_CLIENT_CA_PRIVATE_KEY = "cordaclientcaprivatekey" val CORDA_CLIENT_TLS = "cordaclienttls"
val CORDA_CLIENT_CA = "cordaclientca" 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)) 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. * 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]. * Create a de novo root self-signed X509 v3 CA cert.
* @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.
*/ */
@JvmStatic @JvmStatic
fun createSelfSignedCACert(subject: X500Name, fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
keyPair: KeyPair,
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second) 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) val cert = Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
return CertificateAndKeyPair(cert, keyPair) 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 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 subjectPublicKey subject 's public key.
* @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. * @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. * @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. * Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates.
*/ */
@JvmStatic @JvmStatic
fun createIntermediateCert(subject: X500Name, fun createCertificate(certificateType: CertificateType,
ca: CertificateAndKeyPair, issuerCertificate: X509Certificate, issuerKeyPair: KeyPair,
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair { validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
val keyPair = generateKeyPair(signatureScheme) nameConstraints: NameConstraints? = null): X509Certificate {
val issuer = X509CertificateHolder(ca.certificate.encoded).subject val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate) val cert = Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 1) return cert
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)
} }
/** /**
* Build a certificate path from a trusted root certificate to a target certificate. This will always return a path * 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 trustedRoot trusted root certificate that will be the start of the path.
* @param targetCertAndKey certificate the path ends at. * @param certificates certificates in the path.
* @param revocationEnabled whether revocation of certificates in the path should be checked. * @param revocationEnabled whether revocation of certificates in the path should be checked.
*/ */
fun createCertificatePath(rootCertAndKey: CertificateAndKeyPair, fun createCertificatePath(trustedRoot: X509Certificate, vararg certificates: X509Certificate, revocationEnabled: Boolean): CertPath {
targetCertAndKey: X509Certificate, val certFactory = CertificateFactory.getInstance("X509")
revocationEnabled: Boolean): CertPathBuilderResult { val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
val intermediateCertificates = setOf(targetCertAndKey) params.isRevocationEnabled = revocationEnabled
val certStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(intermediateCertificates)) return certFactory.generateCertPath(certificates.toList())
val certPathFactory = CertPathBuilder.getInstance("PKIX") }
val trustAnchor = TrustAnchor(rootCertAndKey.certificate, null)
val certPathParameters = try { fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
PKIXBuilderParameters(setOf(trustAnchor), X509CertSelector().apply { require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
certificate = targetCertAndKey val certFactory = CertificateFactory.getInstance("X509")
}) val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
} catch (ex: InvalidAlgorithmParameterException) { params.isRevocationEnabled = false
throw RuntimeException(ex) val certPath = certFactory.generateCertPath(certificates.toList())
}.apply { val pathValidator = CertPathValidator.getInstance("PKIX")
addCertStore(certStore) pathValidator.validate(certPath, params)
isRevocationEnabled = revocationEnabled
}
return certPathFactory.build(certPathParameters)
} }
/** /**
@ -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. * 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 storePassword access password for KeyStore.
* @param keyPassword PrivateKey access password for the generated keys. * @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. * 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. * @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. * @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications.
*/ */
fun createKeystoreForSSL(keyStoreFilePath: Path, fun createKeystoreForCordaNode(sslKeyStorePath: Path,
storePassword: String, clientCAKeystorePath: Path,
keyPassword: String, storePassword: String,
caKeyStore: KeyStore, keyPassword: String,
caKeyPassword: String, caKeyStore: KeyStore,
commonName: X500Name, caKeyPassword: String,
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME): KeyStore { legalName: X500Name,
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) {
val rootCA = caKeyStore.getCertificateAndKeyPair(CORDA_ROOT_CA_PRIVATE_KEY, caKeyPassword) val rootCACert = caKeyStore.getX509Certificate(CORDA_ROOT_CA)
val intermediateCA = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, caKeyPassword) val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA, caKeyPassword)
val serverKey = generateKeyPair(signatureScheme) val clientKey = generateKeyPair(signatureScheme)
val host = InetAddress.getLocalHost() val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf())
val serverCert = createTlsServerCert(commonName, serverKey.public, intermediateCA, listOf(host.hostName), listOf(host.hostAddress)) 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 keyPass = keyPassword.toCharArray()
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
keyStore.addOrReplaceKey( val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(clientCAKeystorePath, storePassword)
CORDA_CLIENT_CA_PRIVATE_KEY, clientCAKeystore.addOrReplaceKey(
serverKey.private, CORDA_CLIENT_CA,
clientKey.private,
keyPass, keyPass,
arrayOf(serverCert, intermediateCA.certificate, rootCA.certificate)) arrayOf(clientCACert, intermediateCACert, rootCACert))
keyStore.addOrReplaceCertificate(CORDA_CLIENT_CA, serverCert) clientCAKeystore.save(clientCAKeystorePath, storePassword)
keyStore.save(keyStoreFilePath, storePassword)
return keyStore 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) 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 * Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
* exception. * @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 } 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 * Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
* adds one. * 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 } fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
/** /**
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function. * 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. * @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 { private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
val builder = X500NameBuilder(BCStyle.INSTANCE) val builder = X500NameBuilder(BCStyle.INSTANCE)
var matched = false 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.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.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 X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
val X509Certificate.subject: X500Name get() = X509CertificateHolder(encoded).subject
class CertificateStream(val input: InputStream) { class CertificateStream(val input: InputStream) {
private val certificateFactory = CertificateFactory.getInstance("X.509") 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) 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 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.core.div
import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP
import net.corda.testing.getTestX509Name import net.corda.testing.getTestX509Name
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
@ -33,61 +36,53 @@ class X509UtilitiesTest {
@Test @Test
fun `create valid self-signed CA certificate`() { fun `create valid self-signed CA certificate`() {
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test Cert")) val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
assertTrue { caCertAndKey.certificate.subjectDN.name.contains("CN=Test Cert") } // using our subject common name val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
assertEquals(caCertAndKey.certificate.issuerDN, caCertAndKey.certificate.subjectDN) //self-signed assertTrue { caCert.subjectDN.name.contains("CN=Test Cert") } // using our subject common name
caCertAndKey.certificate.checkValidity(Date()) // throws on verification problems assertEquals(caCert.issuerDN, caCert.subjectDN) //self-signed
caCertAndKey.certificate.verify(caCertAndKey.keyPair.public) // throws on verification problems caCert.checkValidity(Date()) // throws on verification problems
assertTrue { caCertAndKey.certificate.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) caCert.verify(caKey.public) // throws on verification problems
assertTrue { caCertAndKey.certificate.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate 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 @Test
fun `load and save a PEM file certificate`() { fun `load and save a PEM file certificate`() {
val tmpCertificateFile = tempFile("cacert.pem") val tmpCertificateFile = tempFile("cacert.pem")
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test Cert")) val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
X509Utilities.saveCertificateAsPEMFile(caCertAndKey.certificate, tmpCertificateFile) val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile) val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
assertEquals(caCertAndKey.certificate, readCertificate) assertEquals(caCert, readCertificate)
} }
@Test @Test
fun `create valid server certificate chain`() { 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 subjectDN = getTestX509Name("Server Cert")
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val serverCert = X509Utilities.createTlsServerCert(subjectDN, keyPair.public, caCertAndKey, listOf("alias name"), listOf("10.0.0.54")) val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subjectDN, keyPair.public)
assertTrue { serverCert.subjectDN.name.contains("CN=Server Cert") } // using our subject common name 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.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) 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 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 @Test
fun `storing EdDSA key in java keystore`() { fun `storing EdDSA key in java keystore`() {
val tmpKeyStore = tempFile("keystore.jks") 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. // Save the EdDSA private key with self sign cert in the keystore.
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") 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") keyStore.save(tmpKeyStore, "keystorepass")
// Load the keystore from file and make sure keys are intact. // Load the keystore from file and make sure keys are intact.
@ -97,20 +92,21 @@ class X509UtilitiesTest {
assertNotNull(pubKey) assertNotNull(pubKey)
assertNotNull(privateKey) assertNotNull(privateKey)
assertEquals(selfSignCert.keyPair.public, pubKey) assertEquals(keyPair.public, pubKey)
assertEquals(selfSignCert.keyPair.private, privateKey) assertEquals(keyPair.private, privateKey)
} }
@Test @Test
fun `signing EdDSA key with EcDSA certificate`() { fun `signing EdDSA key with EcDSA certificate`() {
val tmpKeyStore = tempFile("keystore.jks") val tmpKeyStore = tempFile("keystore.jks")
val ecDSACert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test")) val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val edDSAKeypair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512") val ecDSACert = createSelfSignedCACertificate(X500Name("CN=Test"), ecDSAKey)
val edDSACert = X509Utilities.createTlsServerCert(X500Name("CN=TestEdDSA"), edDSAKeypair.public, ecDSACert, listOf("alias name"), listOf("10.0.0.54")) 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. // Save the EdDSA private key with cert chains.
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") 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") keyStore.save(tmpKeyStore, "keystorepass")
// Load the keystore from file and make sure keys are intact. // 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 // Load back generated root CA Cert and private key from keystore and check against copy in truststore
val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass") val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass")
val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass") val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY) as X509Certificate val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA, "keypass".toCharArray()) as PrivateKey
val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCaCert, rootCaFromTrustStore) assertEquals(rootCaCert, rootCaFromTrustStore)
rootCaCert.checkValidity(Date()) rootCaCert.checkValidity(Date())
@ -147,24 +143,25 @@ class X509UtilitiesTest {
// Now sign something with private key and verify against certificate public key // Now sign something with private key and verify against certificate public key
val testData = "12345".toByteArray() val testData = "12345".toByteArray()
val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData) val caSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) } assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
// Load back generated intermediate CA Cert and private key // Load back generated intermediate CA Cert and private key
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) as X509Certificate val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA) as X509Certificate
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA, "keypass".toCharArray()) as PrivateKey
intermediateCaCert.checkValidity(Date()) intermediateCaCert.checkValidity(Date())
intermediateCaCert.verify(rootCaCert.publicKey) intermediateCaCert.verify(rootCaCert.publicKey)
// Now sign something with private key and verify against certificate public key // Now sign something with private key and verify against certificate public key
val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData) val intermediateSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) } assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
} }
@Test @Test
fun `create server certificate in keystore for SSL`() { fun `create server certificate in keystore for SSL`() {
val tmpCAKeyStore = tempFile("keystore.jks") val tmpCAKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.jks") val tmpTrustStore = tempFile("truststore.jks")
val tmpSSLKeyStore = tempFile("sslkeystore.jks")
val tmpServerKeyStore = tempFile("serverkeystore.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 // 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 // Load signing intermediate CA cert
val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass") 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 // 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 // Load back server certificate
val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass") 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.checkValidity(Date())
serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey) serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey)
assertTrue { serverCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) } 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 // Now sign something with private key and verify against certificate public key
val testData = "123456".toByteArray() val testData = "123456".toByteArray()
val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData) val signature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) } assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
} }
@Test @Test
fun `create server cert and use in SSL socket`() { fun `create server cert and use in SSL socket`() {
val tmpCAKeyStore = tempFile("keystore.jks") val tmpCAKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.jks") val tmpTrustStore = tempFile("truststore.jks")
val tmpSSLKeyStore = tempFile("sslkeystore.jks")
val tmpServerKeyStore = tempFile("serverkeystore.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 // 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") "trustpass")
// Generate server cert and private key and populate another keystore suitable for SSL // 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 trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
val context = SSLContext.getInstance("TLS") val context = SSLContext.getInstance("TLS")
@ -235,7 +242,7 @@ class X509UtilitiesTest {
arrayOf("TLSv1.2")) arrayOf("TLSv1.2"))
serverParams.wantClientAuth = true serverParams.wantClientAuth = true
serverParams.needClientAuth = 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.sslParameters = serverParams
serverSocket.useClientMode = false serverSocket.useClientMode = false
@ -247,7 +254,7 @@ class X509UtilitiesTest {
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"), "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"),
arrayOf("TLSv1.2")) 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.sslParameters = clientParams
clientSocket.useClientMode = true clientSocket.useClientMode = true
// We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind // 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 peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
val x500name = X500Name(peerX500Principal.name) val x500name = X500Name(peerX500Principal.name)
assertEquals(MEGA_CORP.name, x500name) assertEquals(MEGA_CORP.name, x500name)
X509Utilities.validateCertificateChain(trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), *peerChain)
val output = DataOutputStream(clientSocket.outputStream) val output = DataOutputStream(clientSocket.outputStream)
output.writeUTF("Hello World") output.writeUTF("Hello World")
var timeout = 0 var timeout = 0
@ -324,36 +330,40 @@ class X509UtilitiesTest {
trustStoreFilePath: Path, trustStoreFilePath: Path,
trustStorePassword: String trustStorePassword: String
): KeyStore { ): KeyStore {
val rootCA = X509Utilities.createSelfSignedCACert(X509Utilities.getDevX509Name("Corda Node Root CA")) val rootCAKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCA = X509Utilities.createIntermediateCert(X509Utilities.getDevX509Name("Corda Node Intermediate CA"), rootCA) 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 keyPass = keyPassword.toCharArray()
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword) 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, keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA,
intermediateCA.keyPair.private, intermediateCAKeyPair.private,
keyPass, keyPass,
arrayOf(intermediateCA.certificate, rootCA.certificate)) arrayOf(intermediateCACert, rootCACert))
keyStore.save(keyStoreFilePath, storePassword) keyStore.save(keyStoreFilePath, storePassword)
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword) val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCA.certificate) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCA.certificate) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert)
trustStore.save(trustStoreFilePath, trustStorePassword) trustStore.save(trustStoreFilePath, trustStorePassword)
return keyStore return keyStore
} }
@Test @Test
fun `Get correct private key type from Keystore`() { fun `Get correct private key type from Keystore`() {
val keyPair = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), keyPair) val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") 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 keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword") val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")

View File

@ -143,7 +143,7 @@ class KryoTests {
@Test @Test
fun `serialize - deserialize X509Certififcate`() { 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 serialized = expected.serialize(kryo).bytes
val actual: X509Certificate = serialized.deserialize(kryo) val actual: X509Certificate = serialized.deserialize(kryo)
assertEquals(expected, actual) assertEquals(expected, actual)
@ -151,9 +151,10 @@ class KryoTests {
@Test @Test
fun `serialize - deserialize X509CertPath`() { fun `serialize - deserialize X509CertPath`() {
val rootCA = X509Utilities.createSelfSignedCACert(ALICE.name) val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val certificate = X509Utilities.createTlsServerCert(BOB.name, BOB_PUBKEY, rootCA, emptyList(), emptyList()) val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
val expected = X509Utilities.createCertificatePath(rootCA, certificate, false).certPath 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 serialized = expected.serialize(kryo).bytes
val actual: CertPath = serialized.deserialize(kryo) val actual: CertPath = serialized.deserialize(kryo)
assertEquals(expected, actual) 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 point we will support the ability for a node to have multiple versions of the same flow registered, enabling backwards
compatibility of CorDapp flows. 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 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 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 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. This demo shows a party getting transactions notarised by either a single-node or a distributed notary service.
The demo will start three distributed notary nodes, and two counterparty nodes. One of the counterparties will generate transactions All versions of the demo start two counterparty nodes.
that transfer a self-issued asset to the other party and submit them for notarisation. 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, 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. 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). 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``. 1. Run ``./gradlew samples:notary-demo:deployNodesRaft``, which will create node directories with configs under ``samples/notary-demo/build/nodes``.
2. Run ``./samples/raft-notary-demo/build/nodes/runnodes``, which will start the nodes in separate terminal windows/tabs. 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 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 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: 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``. 1. Run ``gradlew samples:notary-demo:deployNodesRaft``, which will create node directories with configs under ``samples\notary-demo\build\nodes``.
2. Run ``samples\raft-notary-demo\build\nodes\runnodes``, which will start the nodes in separate terminal windows/tabs. 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 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 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. 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 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: 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.loadKeyStore
import net.corda.core.crypto.KeyStoreUtilities.loadOrCreateKeyStore 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
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
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY import net.corda.core.crypto.X509Utilities.createCertificate
import net.corda.core.crypto.X509Utilities.createIntermediateCert
import net.corda.core.crypto.X509Utilities.createTlsServerCert
import net.corda.core.seconds import net.corda.core.seconds
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.configureDatabase
import org.bouncycastle.asn1.x500.X500Name 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.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.ServerConnector
@ -83,8 +83,7 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CertificateAnd
for (id in storage.getApprovedRequestIds()) { for (id in storage.getApprovedRequestIds()) {
storage.approveRequest(id) { storage.approveRequest(id) {
val request = JcaPKCS10CertificationRequest(request) val request = JcaPKCS10CertificationRequest(request)
createTlsServerCert(request.subject, request.publicKey, caCertAndKey, createCertificate(CertificateType.CLIENT_CA, caCertAndKey.certificate, caCertAndKey.keyPair, request.subject, request.publicKey, nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))), arrayOf()))
if (ipAddress == hostName) listOf() else listOf(hostName), listOf(ipAddress))
} }
logger.info("Approved request $id") logger.info("Approved request $id")
serverStatus.lastApprovalTime = Instant.now() serverStatus.lastApprovalTime = Instant.now()
@ -137,19 +136,20 @@ private fun DoormanParameters.generateRootKeyPair() {
val rootStore = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword) val rootStore = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword)
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ") val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ")
if (rootStore.containsAlias(CORDA_ROOT_CA_PRIVATE_KEY)) { if (rootStore.containsAlias(CORDA_ROOT_CA)) {
val oldKey = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA_PRIVATE_KEY).publicKey val oldKey = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA).publicKey
println("Key $CORDA_ROOT_CA_PRIVATE_KEY already exists in keystore, process will now terminate.") println("Key $CORDA_ROOT_CA already exists in keystore, process will now terminate.")
println(oldKey) println(oldKey)
exitProcess(1) exitProcess(1)
} }
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name(CORDA_ROOT_CA)) val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
rootStore.addOrReplaceKey(CORDA_ROOT_CA_PRIVATE_KEY, selfSignCert.keyPair.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate)) val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name(CORDA_ROOT_CA), selfSignKey)
rootStore.addOrReplaceKey(CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert))
rootStore.save(rootStorePath, rootKeystorePassword) rootStore.save(rootStorePath, rootKeystorePassword)
println("Root CA keypair and certificate stored in $rootStorePath.") 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() { private fun DoormanParameters.generateCAKeyPair() {
@ -159,7 +159,7 @@ private fun DoormanParameters.generateCAKeyPair() {
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ") val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ")
val rootKeyStore = loadKeyStore(rootStorePath, rootKeystorePassword) 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 keystorePassword = keystorePassword ?: readPassword("Keystore Password: ")
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ") val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ")
@ -167,19 +167,20 @@ private fun DoormanParameters.generateCAKeyPair() {
keystorePath.parent.createDirectories() keystorePath.parent.createDirectories()
val keyStore = loadOrCreateKeyStore(keystorePath, keystorePassword) val keyStore = loadOrCreateKeyStore(keystorePath, keystorePassword)
if (keyStore.containsAlias(CORDA_INTERMEDIATE_CA_PRIVATE_KEY)) { if (keyStore.containsAlias(CORDA_INTERMEDIATE_CA)) {
val oldKey = loadOrCreateKeyStore(keystorePath, rootKeystorePassword).getCertificate(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).publicKey val oldKey = loadOrCreateKeyStore(keystorePath, rootKeystorePassword).getCertificate(CORDA_INTERMEDIATE_CA).publicKey
println("Key $CORDA_INTERMEDIATE_CA_PRIVATE_KEY already exists in keystore, process will now terminate.") println("Key $CORDA_INTERMEDIATE_CA already exists in keystore, process will now terminate.")
println(oldKey) println(oldKey)
exitProcess(1) exitProcess(1)
} }
val intermediateKeyAndCert = createIntermediateCert(X500Name(CORDA_INTERMEDIATE_CA), rootKeyAndCert) val intermediateKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, intermediateKeyAndCert.keyPair.private, val intermediateCert = createCertificate(CertificateType.INTERMEDIATE_CA, rootKeyAndCert.certificate, rootKeyAndCert.keyPair, X500Name(CORDA_INTERMEDIATE_CA), intermediateKey.public)
caPrivateKeyPassword.toCharArray(), arrayOf(intermediateKeyAndCert.certificate, rootKeyAndCert.certificate)) keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA, intermediateKey.private,
caPrivateKeyPassword.toCharArray(), arrayOf(intermediateCert, rootKeyAndCert.certificate))
keyStore.save(keystorePath, keystorePassword) keyStore.save(keystorePath, keystorePassword)
println("Intermediate CA keypair and certificate stored in $keystorePath.") 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() { private fun DoormanParameters.startDoorman() {
@ -189,8 +190,8 @@ private fun DoormanParameters.startDoorman() {
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ") val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ")
val keystore = loadOrCreateKeyStore(keystorePath, keystorePassword) val keystore = loadOrCreateKeyStore(keystorePath, keystorePassword)
val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).last() val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA).last()
val caCertAndKey = keystore.getCertificateAndKeyPair(caPrivateKeyPassword, CORDA_INTERMEDIATE_CA_PRIVATE_KEY) val caCertAndKey = keystore.getCertificateAndKeyPair(caPrivateKeyPassword, CORDA_INTERMEDIATE_CA)
// Create DB connection. // Create DB connection.
val (datasource, database) = configureDatabase(dataSourceProperties) 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.CertificateResponse
import com.r3.corda.doorman.persistence.CertificationRequestData import com.r3.corda.doorman.persistence.CertificationRequestData
import com.r3.corda.doorman.persistence.CertificationRequestStorage import com.r3.corda.doorman.persistence.CertificationRequestStorage
import net.corda.core.crypto.CertificateStream import net.corda.core.crypto.*
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -29,12 +26,14 @@ import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals import kotlin.test.assertEquals
class DoormanServiceTest { class DoormanServiceTest {
private val rootCA = X509Utilities.createSelfSignedCACert(X500Name("CN=Corda Node Root CA,L=London")) private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val intermediateCA = X509Utilities.createSelfSignedCACert(X500Name("CN=Corda Node Intermediate CA,L=London")) 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 lateinit var doormanServer: DoormanServer
private fun startSigningServer(storage: CertificationRequestStorage) { 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() doormanServer.start()
} }
@ -90,8 +89,7 @@ class DoormanServiceTest {
storage.approveRequest(id) { storage.approveRequest(id) {
JcaPKCS10CertificationRequest(request).run { JcaPKCS10CertificationRequest(request).run {
X509Utilities.createTlsServerCert(subject, publicKey, intermediateCA, X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey)
if (ipAddress == hostName) listOf() else listOf(hostName), listOf(ipAddress))
} }
} }

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.CertificateResponse
import com.r3.corda.doorman.persistence.CertificationRequestData import com.r3.corda.doorman.persistence.CertificationRequestData
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
import net.corda.core.crypto.CertificateType
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
@ -21,7 +22,8 @@ import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
class DBCertificateRequestStorageTest { 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 var closeDb: Closeable? = null
private lateinit var storage: DBCertificateRequestStorage private lateinit var storage: DBCertificateRequestStorage
@ -137,12 +139,12 @@ class DBCertificateRequestStorageTest {
private fun approveRequest(requestId: String) { private fun approveRequest(requestId: String) {
storage.approveRequest(requestId) { storage.approveRequest(requestId) {
JcaPKCS10CertificationRequest(request).run { JcaPKCS10CertificationRequest(request).run {
X509Utilities.createTlsServerCert( X509Utilities.createCertificate(
CertificateType.TLS,
intermediateCACert,
intermediateCAKey,
subject, subject,
publicKey, publicKey)
intermediateCA,
if (ipAddress == hostName) listOf() else listOf(hostName),
listOf(ipAddress))
} }
} }
} }

View File

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

View File

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

View File

@ -7,6 +7,7 @@ interface SSLConfiguration {
val keyStorePassword: String val keyStorePassword: String
val trustStorePassword: String val trustStorePassword: String
val certificatesDirectory: Path 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" 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=${quasarExcludeExpression}"]
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"] javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"]
systemProperties['visualvm.display.name'] = 'Corda' systemProperties['visualvm.display.name'] = 'Corda'
systemProperties['jdk.serialFilter'] = 'maxbytes=0'
minJavaVersion = '1.8.0' minJavaVersion = '1.8.0'
minUpdateVersion['1.8'] = java8_minUpdateVersion minUpdateVersion['1.8'] = java8_minUpdateVersion
caplets = ['CordaCaplet'] caplets = ['CordaCaplet']

View File

@ -53,7 +53,6 @@ class BootTests {
class ObjectInputStreamFlow : FlowLogic<Unit>() { class ObjectInputStreamFlow : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { 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() val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
ObjectInputStream(data.inputStream()).use { it.readObject() } 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.StateRef
import net.corda.core.contracts.TransactionType import net.corda.core.contracts.TransactionType
import net.corda.core.crypto.appendToCommonName 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.getOrThrow
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.ALICE import net.corda.core.utilities.ALICE
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.flows.NotaryError import net.corda.flows.NotaryError
import net.corda.flows.NotaryException import net.corda.flows.NotaryException
import net.corda.flows.NotaryFlow import net.corda.flows.NotaryFlow
import net.corda.node.internal.AbstractNode import net.corda.node.internal.AbstractNode
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.services.transactions.BFTNonValidatingNotaryService 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.ServiceIdentityGenerator
import net.corda.node.utilities.transaction import net.corda.node.utilities.transaction
import net.corda.testing.node.NodeBasedTest import net.corda.testing.node.NodeBasedTest
@ -28,71 +28,55 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class BFTNotaryServiceTests : NodeBasedTest() { 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 @Test
fun `detect double spend`() { 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 alice = startNode(ALICE.name).getOrThrow()
val notaryParty = alice.netMapCache.getNotary(clusterName)!!
val notaryParty = alice.netMapCache.getNotary(notaryCommonName)!!
val inputState = issueState(alice, notaryParty) val inputState = issueState(alice, notaryParty)
val firstTxBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState) val firstTxBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState)
val firstSpendTx = alice.services.signInitialTransaction(firstTxBuilder) val firstSpendTx = alice.services.signInitialTransaction(firstTxBuilder)
alice.services.startFlow(NotaryFlow.Client(firstSpendTx)).resultFuture.getOrThrow()
val firstSpend = alice.services.startFlow(NotaryFlow.Client(firstSpendTx)) val secondSpendBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState).also {
firstSpend.resultFuture.getOrThrow() it.addOutputState(DummyContract.SingleOwnerState(0, alice.info.legalIdentity))
val secondSpendBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState).run {
val dummyState = DummyContract.SingleOwnerState(0, alice.info.legalIdentity)
addOutputState(dummyState)
this
} }
val secondSpendTx = alice.services.signInitialTransaction(secondSpendBuilder) val secondSpendTx = alice.services.signInitialTransaction(secondSpendBuilder)
val secondSpend = alice.services.startFlow(NotaryFlow.Client(secondSpendTx)) val secondSpend = alice.services.startFlow(NotaryFlow.Client(secondSpendTx))
val ex = assertFailsWith(NotaryException::class) {
val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() } secondSpend.resultFuture.getOrThrow()
}
val error = ex.error as NotaryError.Conflict val error = ex.error as NotaryError.Conflict
assertEquals(error.txId, secondSpendTx.id) assertEquals(error.txId, secondSpendTx.id)
} }
private fun issueState(node: AbstractNode, notary: Party): StateAndRef<*> { private fun issueState(node: AbstractNode, notary: Party) = node.run {
return node.database.transaction { database.transaction {
val builder = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0)) val builder = DummyContract.generateInitial(Random().nextInt(), notary, info.legalIdentity.ref(0))
val stx = node.services.signInitialTransaction(builder) val stx = services.signInitialTransaction(builder)
node.services.recordTransactions(listOf(stx)) services.recordTransactions(listOf(stx))
StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0)) StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0))
} }
} }
private fun startBFTNotaryCluster(notaryName: X500Name, private fun startBFTNotaryCluster(clusterName: X500Name,
clusterSize: Int, clusterSize: Int,
serviceType: ServiceType): List<Node> { serviceType: ServiceType) {
require(clusterSize > 0) require(clusterSize > 0)
val quorum = (2 * clusterSize + 1) / 3 val replicaNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
ServiceIdentityGenerator.generateToDisk( ServiceIdentityGenerator.generateToDisk(
(0 until clusterSize).map { tempFolder.root.toPath() / "${notaryName.commonName}-$it" }, replicaNames.map { baseDirectory(it) },
serviceType.id, serviceType.id,
notaryName, clusterName,
quorum) minCorrectReplicas(clusterSize))
val serviceInfo = ServiceInfo(serviceType, clusterName)
val serviceInfo = ServiceInfo(serviceType, notaryName) val notaryClusterAddresses = (0 until clusterSize).map { "localhost:${11000 + it * 10}" }
val nodes = (0 until clusterSize).map { (0 until clusterSize).forEach {
startNode( startNode(
buildNodeName(it, notaryName), replicaNames[it],
advertisedServices = setOf(serviceInfo), advertisedServices = setOf(serviceInfo),
configOverrides = mapOf("notaryNodeId" to it) configOverrides = mapOf("bftReplicaId" to it, "notaryClusterAddresses" to notaryClusterAddresses)
).getOrThrow() ).getOrThrow()
} }
return nodes
} }
} }

View File

@ -1,14 +1,26 @@
package net.corda.services.messaging 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.NODE_USER
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.RPCApi 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 net.corda.testing.messaging.SimpleMQClient
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType 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 org.junit.Test
import java.nio.file.Files
/** /**
* Runs the security tests with the attacker pretending to be a node on the network. * 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 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.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.* 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.MessageRecipients
import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.node.services.DEFAULT_SESSION_ID
@ -64,10 +62,8 @@ class P2PMessagingTest : NodeBasedTest() {
// TODO Use a dummy distributed service // TODO Use a dummy distributed service
@Test @Test
fun `communicating with a distributed service which the network map node is part of`() { fun `communicating with a distributed service which the network map node is part of`() {
val root = tempFolder.root.toPath()
ServiceIdentityGenerator.generateToDisk( 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, RaftValidatingNotaryService.type.id,
DISTRIBUTED_SERVICE_NAME) DISTRIBUTED_SERVICE_NAME)

View File

@ -2,8 +2,6 @@ package net.corda.services.messaging
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.crypto.X509Utilities 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.getOrThrow
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -60,7 +58,7 @@ class P2PSecurityTest : NodeBasedTest() {
private fun startSimpleNode(legalName: X500Name): SimpleNode { private fun startSimpleNode(legalName: X500Name): SimpleNode {
val config = TestNodeConfiguration( val config = TestNodeConfiguration(
baseDirectory = tempFolder.root.toPath() / legalName.commonName, baseDirectory = baseDirectory(legalName),
myLegalName = legalName, myLegalName = legalName,
networkMapService = NetworkMapInfo(networkMapNode.configuration.p2pAddress, networkMapNode.info.legalIdentity.name)) 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 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.crypto.orgName
import net.corda.core.node.VersionInfo import net.corda.core.node.VersionInfo
import net.corda.core.utilities.Emoji import net.corda.core.utilities.Emoji
import net.corda.core.utilities.LogHelper.withLevel
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.internal.enforceSingleNodeIsRunning import net.corda.node.internal.enforceSingleNodeIsRunning
import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.transactions.bftSMaRtSerialFilter
import net.corda.node.shell.InteractiveShell import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.node.utilities.registration.NetworkRegistrationHelper
@ -21,7 +21,6 @@ import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole import org.fusesource.jansi.AnsiConsole
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.bridge.SLF4JBridgeHandler import org.slf4j.bridge.SLF4JBridgeHandler
import java.io.*
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.net.InetAddress import java.net.InetAddress
import java.nio.file.Paths import java.nio.file.Paths
@ -72,8 +71,6 @@ fun main(args: Array<String>) {
enforceSingleNodeIsRunning(cmdlineOptions.baseDirectory) enforceSingleNodeIsRunning(cmdlineOptions.baseDirectory)
initLogging(cmdlineOptions) initLogging(cmdlineOptions)
disableJavaDeserialization() // Should be after initLogging to avoid TMI.
// Manifest properties are only available if running from the corda jar // 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 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}") println("Unable to load the configuration file: ${e.rootCause.message}")
exitProcess(2) exitProcess(2)
} }
SerialFilter.install(if (conf.bftReplicaId != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
if (cmdlineOptions.isRegistration) { if (cmdlineOptions.isRegistration) {
println() println()
println("******************************************************************") println("******************************************************************")
@ -208,29 +205,12 @@ private fun assertCanNormalizeEmptyPath() {
} }
} }
private fun failStartUp(message: String): Nothing { internal fun failStartUp(message: String): Nothing {
println(message) println(message)
println("Corda will now exit...") println("Corda will now exit...")
exitProcess(1) 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) { private fun printPluginsAndServices(node: Node) {
node.configuration.extraAdvertisedServiceIds.let { node.configuration.extraAdvertisedServiceIds.let {
if (it.isNotEmpty()) printBasicNodeInfo("Providing network services", it.joinToString()) 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.nodeapi.config.parseAs
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
import net.corda.cordform.CordformContext import net.corda.cordform.CordformContext
import net.corda.core.internal.ShutdownHook
import net.corda.core.internal.addShutdownHook
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
@ -236,22 +238,19 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
coerce: (D) -> DI, coerce: (D) -> DI,
dsl: DI.() -> A dsl: DI.() -> A
): A { ): A {
var shutdownHook: Thread? = null var shutdownHook: ShutdownHook? = null
try { try {
driverDsl.start() driverDsl.start()
shutdownHook = Thread({ shutdownHook = addShutdownHook {
driverDsl.shutdown() driverDsl.shutdown()
}) }
Runtime.getRuntime().addShutdownHook(shutdownHook)
return dsl(coerce(driverDsl)) return dsl(coerce(driverDsl))
} catch (exception: Throwable) { } catch (exception: Throwable) {
log.error("Driver shutting down because of exception", exception) log.error("Driver shutting down because of exception", exception)
throw exception throw exception
} finally { } finally {
driverDsl.shutdown() driverDsl.shutdown()
if (shutdownHook != null) { shutdownHook?.cancel()
Runtime.getRuntime().removeShutdownHook(shutdownHook)
}
} }
} }
@ -558,21 +557,19 @@ class DriverDSL(
verifierType: VerifierType, verifierType: VerifierType,
rpcUsers: List<User> rpcUsers: List<User>
): ListenableFuture<Pair<Party, List<NodeHandle>>> { ): 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) } val paths = nodeNames.map { baseDirectory(it) }
ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName) ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName)
val advertisedServices = setOf(ServiceInfo(type, notaryName))
val serviceInfo = ServiceInfo(type, notaryName)
val advertisedService = setOf(serviceInfo)
val notaryClusterAddress = portAllocation.nextHostAndPort() val notaryClusterAddress = portAllocation.nextHostAndPort()
// Start the first node that will bootstrap the cluster // 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 // All other nodes will join the cluster
val restNotaryFutures = nodeNames.drop(1).map { val restNotaryFutures = nodeNames.drop(1).map {
val nodeAddress = portAllocation.nextHostAndPort() val nodeAddress = portAllocation.nextHostAndPort()
val configOverride = mapOf("notaryNodeAddress" to nodeAddress.toString(), "notaryClusterAddresses" to listOf(notaryClusterAddress.toString())) 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 -> return firstNotaryFuture.flatMap { firstNotary ->

View File

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

View File

@ -1,7 +1,7 @@
package net.corda.node.internal package net.corda.node.internal
import net.corda.core.internal.addShutdownHook
import net.corda.core.div import net.corda.core.div
import net.corda.core.utilities.loggerFor
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.nio.file.Path 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 // 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. // when our process shuts down, but we try in stop() anyway just to be nice.
Runtime.getRuntime().addShutdownHook(Thread { addShutdownHook {
pidFileLock.release() pidFileLock.release()
}) }
val ourProcessID: String = ManagementFactory.getRuntimeMXBean().name.split("@")[0] val ourProcessID: String = ManagementFactory.getRuntimeMXBean().name.split("@")[0]
pidFileRw.setLength(0) pidFileRw.setLength(0)
pidFileRw.write(ourProcessID.toByteArray()) 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.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture 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.messaging.RPCOps
import net.corda.core.minutes
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.VersionInfo import net.corda.core.node.VersionInfo
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.UniquenessProvider 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.loggerFor
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.node.printBasicNodeInfo import net.corda.node.printBasicNodeInfo
@ -47,7 +46,6 @@ import java.io.IOException
import java.time.Clock import java.time.Clock
import java.util.* import java.util.*
import javax.management.ObjectName 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], * 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 var messageBroker: ArtemisMessagingServer? = null
private var shutdownThread: Thread? = null private var shutdownHook: ShutdownHook? = null
private lateinit var userService: RPCUserService private lateinit var userService: RPCUserService
@ -295,12 +293,9 @@ class Node(override val configuration: FullNodeConfiguration,
(startupComplete as SettableFuture<Unit>).set(Unit) (startupComplete as SettableFuture<Unit>).set(Unit)
} }
shutdownHook = addShutdownHook {
shutdownThread = thread(start = false) {
stop() stop()
} }
Runtime.getRuntime().addShutdownHook(shutdownThread)
return this return this
} }
@ -322,12 +317,9 @@ class Node(override val configuration: FullNodeConfiguration,
synchronized(this) { synchronized(this) {
if (shutdown) return if (shutdown) return
shutdown = true shutdown = true
// Unregister shutdown hook to prevent any unnecessary second calls to stop // Unregister shutdown hook to prevent any unnecessary second calls to stop
if ((shutdownThread != null) && (Thread.currentThread() != shutdownThread)) { shutdownHook?.cancel()
Runtime.getRuntime().removeShutdownHook(shutdownThread) shutdownHook = null
shutdownThread = null
}
} }
printBasicNodeInfo("Shutting down ...") printBasicNodeInfo("Shutting down ...")

View File

@ -54,8 +54,8 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: X500Name) {
if (!trustStoreFile.exists()) { if (!trustStoreFile.exists()) {
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) 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") 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 // 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 messagingServerAddress: HostAndPort?,
val extraAdvertisedServiceIds: List<String>, val extraAdvertisedServiceIds: List<String>,
val notaryNodeId: Int?, val bftReplicaId: Int?,
val notaryNodeAddress: HostAndPort?, val notaryNodeAddress: HostAndPort?,
val notaryClusterAddresses: List<HostAndPort>, val notaryClusterAddresses: List<HostAndPort>,
override val certificateChainCheckPolicies: List<CertChainPolicyConfig>, 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 io.netty.handler.ssl.SslHandler
import net.corda.core.* import net.corda.core.*
import net.corda.core.crypto.* 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.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache 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.CoreQueueConfiguration
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration 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.*
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.security.Role import org.apache.activemq.artemis.core.security.Role
import org.apache.activemq.artemis.core.server.ActiveMQServer import org.apache.activemq.artemis.core.server.ActiveMQServer
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl 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.CertificateCallback
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal 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.spi.core.security.jaas.UserPrincipal
import org.apache.activemq.artemis.utils.ConfigurationHelper
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import rx.Subscription import rx.Subscription
import sun.security.x509.X509CertImpl
import java.io.IOException import java.io.IOException
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyStore import java.security.KeyStore
@ -259,9 +259,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
@Throws(IOException::class, KeyStoreException::class) @Throws(IOException::class, KeyStoreException::class)
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager { 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 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) val ourSubjectDN = X500Name(ourCertificate.subjectDN.name)
// This is a sanity check and should not fail unless things have been misconfigured // 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" private fun getBridgeName(queueName: String, hostAndPort: HostAndPort): String = "$queueName -> $hostAndPort"
// This is called on one of Artemis' background threads // This is called on one of Artemis' background threads
internal fun hostVerificationFail(peerLegalName: X500Name, expectedLegalName: X500Name) { internal fun hostVerificationFail(expectedLegalName: X500Name, errorMsg: String?) {
log.error("Peer has wrong CN - expected $expectedLegalName but got $peerLegalName. This is either a fatal " + log.error(errorMsg)
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!")
if (expectedLegalName == config.networkMapService?.legalName) { 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! // 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")) _networkMapConnectionFuture!!.setException(IOException("${config.networkMapService} failed host verification check"))
@ -466,7 +465,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
} }
class VerifyingNettyConnectorFactory : NettyConnectorFactory() { class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
override fun createConnector(configuration: MutableMap<String, Any>?, override fun createConnector(configuration: MutableMap<String, Any>,
handler: BufferHandler?, handler: BufferHandler?,
listener: ClientConnectionLifeCycleListener?, listener: ClientConnectionLifeCycleListener?,
closeExecutor: Executor?, 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?, handler: BufferHandler?,
listener: ClientConnectionLifeCycleListener?, listener: ClientConnectionLifeCycleListener?,
closeExecutor: Executor?, closeExecutor: Executor?,
@ -486,27 +485,37 @@ private class VerifyingNettyConnector(configuration: MutableMap<String, Any>?,
scheduledThreadPool: ScheduledExecutorService?, scheduledThreadPool: ScheduledExecutorService?,
protocolManager: ClientProtocolManager?) : protocolManager: ClientProtocolManager?) :
NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) { NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) {
private val server = configuration?.get(ArtemisMessagingServer::class.java.name) as? ArtemisMessagingServer private val server = configuration[ArtemisMessagingServer::class.java.name] as ArtemisMessagingServer
private val expecteLegalName: X500Name? = configuration?.get(ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME) as X500Name? private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration)
override fun createConnection(): Connection? { override fun createConnection(): Connection? {
val connection = super.createConnection() as NettyConnection? val connection = super.createConnection() as? NettyConnection
if (connection != null && expecteLegalName != null) { if (sslEnabled && connection != null) {
val peerLegalName: X500Name = connection val expectedLegalName = configuration[ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME] as X500Name
.channel try {
.pipeline() val session = connection.channel
.get(SslHandler::class.java) .pipeline()
.engine() .get(SslHandler::class.java)
.session .engine()
.peerPrincipal .session
.name // Checks the peer name is the one we are expecting.
.let(::X500Name) val peerLegalName = session.peerPrincipal.name.let(::X500Name)
if (peerLegalName != expecteLegalName) { 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() connection.close()
server!!.hostVerificationFail(peerLegalName, expecteLegalName) server.hostVerificationFail(expectedLegalName, e.message)
return null // Artemis will keep trying to reconnect until it's told otherwise return null
} else {
server!!.onTcpConnection(peerLegalName)
} }
} }
return connection return connection
@ -547,7 +556,7 @@ sealed class CertificateChainCheckPolicy {
object LeafMustMatch : CertificateChainCheckPolicy() { object LeafMustMatch : CertificateChainCheckPolicy() {
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check { 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 { return object : Check {
override fun checkCertificateChain(theirChain: Array<X509Certificate>) { override fun checkCertificateChain(theirChain: Array<X509Certificate>) {
val theirLeaf = theirChain.first().publicKey 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.flows.NotaryException
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import java.nio.file.Path
import kotlin.concurrent.thread 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. * 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, timestampChecker: TimestampChecker,
serverId: Int, serverId: Int,
db: Database, db: Database,
val client: BFTSMaRt.Client) : NotaryService { private val client: BFTSMaRt.Client) : NotaryService {
init { init {
val configHandle = config.handle()
thread(name = "BFTSmartServer-$serverId", isDaemon = true) { 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, db: Database,
tableName: String, tableName: String,
services: ServiceHubInternal, 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 { override fun executeCommand(command: ByteArray): ByteArray {
val request = command.deserialize<BFTSMaRt.CommitRequest>() 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.JDBCHashMap
import net.corda.node.utilities.transaction import net.corda.node.utilities.transaction
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import java.nio.file.Path
import java.util.* import java.util.*
/** /**
@ -66,13 +67,17 @@ object BFTSMaRt {
data class Signatures(val txSignatures: List<DigitalSignature>) : ClusterResponse() 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 { companion object {
private val log = loggerFor<Client>() private val log = loggerFor<Client>()
} }
/** A proxy for communicating with the BFT cluster */ /** 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 * 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 return response
} }
private fun buildProxy(): ServiceProxy { private fun buildProxy(configHome: Path): ServiceProxy {
val comparator = buildResponseComparator() val comparator = buildResponseComparator()
val extractor = buildExtractor() 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. */ /** 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 accepted = responses.filterIsInstance<ReplicaResponse.Signature>()
val rejected = responses.filterIsInstance<ReplicaResponse.Error>() 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: 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 // 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. * The validation logic can be specified by implementing the [executeCommand] method.
*/ */
@Suppress("LeakingThis") @Suppress("LeakingThis")
abstract class Server(val id: Int, abstract class Server(configHome: Path,
val replicaId: Int,
val db: Database, val db: Database,
tableName: String, tableName: String,
val services: ServiceHubInternal, val services: ServiceHubInternal,
@ -152,7 +158,7 @@ object BFTSMaRt {
init { init {
// TODO: Looks like this statement is blocking. Investigate the bft-smart node startup. // 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? { 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() return NettyTransport.builder()
.withSsl() .withSsl()
.withSslProtocol(SslProtocol.TLSv1_2) .withSslProtocol(SslProtocol.TLSv1_2)
.withKeyStorePath(config.keyStoreFile.toString()) .withKeyStorePath(config.sslKeystore.toString())
.withKeyStorePassword(config.keyStorePassword) .withKeyStorePassword(config.keyStorePassword)
.withTrustStorePath(config.trustStoreFile.toString()) .withTrustStorePath(config.trustStoreFile.toString())
.withTrustStorePassword(config.trustStorePassword) .withTrustStorePassword(config.trustStorePassword)

View File

@ -3,6 +3,7 @@ package net.corda.node.utilities.registration
import net.corda.core.* import net.corda.core.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA 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.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.openssl.jcajce.JcaPEMWriter
@ -31,15 +32,16 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
fun buildKeystore() { fun buildKeystore() {
config.certificatesDirectory.createDirectories() config.certificatesDirectory.createDirectories()
val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword) val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.nodeKeystore, keystorePassword)
if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) { if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) {
// Create or load self signed keypair from the key store. // Create or load self signed keypair from the key store.
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
if (!caKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) { 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. // Save to the key store.
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, selfSignCert.keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate)) caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert))
caKeyStore.save(config.keyStoreFile, keystorePassword) caKeyStore.save(config.nodeKeystore, keystorePassword)
} }
val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
val requestId = submitOrResumeCertificateSigningRequest(keyPair) 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. // Save private key and certificate chain to the key store.
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
caKeyStore.save(config.keyStoreFile, keystorePassword) caKeyStore.save(config.nodeKeystore, keystorePassword)
// Save root certificates to trust store. // Save root certificates to trust store.
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
// Assumes certificate chain always starts with client certificate and end with root certificate. // Assumes certificate chain always starts with client certificate and end with root certificate.
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last()) trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last())
trustStore.save(config.trustStoreFile, config.trustStorePassword) 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. // All done, clean up temp files.
requestIdStore.deleteIfExists() requestIdStore.deleteIfExists()
} else { } else {
@ -72,6 +83,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
} }
} }
/** /**
* Poll Certificate Signing Server for approved certificate, * Poll Certificate Signing Server for approved certificate,
* enter a slow polling loop if server return null. * 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 #Number of servers in the group
system.servers.num = 4 system.servers.num = %s
#Maximum number of faulty replicas #Maximum number of faulty replicas
system.servers.f = 1 system.servers.f = %s
#Timeout to asking for a client request #Timeout to asking for a client request
system.totalordermulticast.timeout = 2000 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.div
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.utilities.ALICE import net.corda.core.utilities.ALICE
import net.corda.core.utilities.WHITESPACE
import net.corda.testing.node.NodeBasedTest import net.corda.testing.node.NodeBasedTest
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
@ -12,11 +13,8 @@ import org.junit.Test
class NodeTest : NodeBasedTest() { class NodeTest : NodeBasedTest() {
@Test @Test
fun `empty plugins directory`() { fun `empty plugins directory`() {
val baseDirectory = tempFolder.root.toPath() / ALICE.name.commonName val baseDirectory = baseDirectory(ALICE.name)
(baseDirectory / "plugins").createDirectories() (baseDirectory / "plugins").createDirectories()
val node = startNode(ALICE.name).getOrThrow() startNode(ALICE.name).getOrThrow()
// Make sure we created the plugins dir in the correct place
assertThat(baseDirectory).isEqualTo(node.configuration.baseDirectory)
} }
} }

View File

@ -1,7 +1,6 @@
package net.corda.node.services.network package net.corda.node.services.network
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.*
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
@ -66,13 +65,13 @@ class InMemoryIdentityServiceTests {
*/ */
@Test @Test
fun `assert unknown anonymous key is unrecognised`() { fun `assert unknown anonymous key is unrecognised`() {
val rootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name) val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val txCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, rootCertAndKey) val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey)
val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val service = InMemoryIdentityService() val service = InMemoryIdentityService()
val rootKey = rootCertAndKey.keyPair
// TODO: Generate certificate with an EdDSA key rather than ECDSA // TODO: Generate certificate with an EdDSA key rather than ECDSA
val identity = Party(rootCertAndKey) val identity = Party(CertificateAndKeyPair(rootCert, rootKey))
val txIdentity = AnonymousParty(txCertAndKey.keyPair.public) val txIdentity = AnonymousParty(txKey.public)
assertFailsWith<IdentityService.UnknownAnonymousPartyException> { assertFailsWith<IdentityService.UnknownAnonymousPartyException> {
service.assertOwnership(identity, txIdentity) service.assertOwnership(identity, txIdentity)
@ -85,20 +84,26 @@ class InMemoryIdentityServiceTests {
*/ */
@Test @Test
fun `assert ownership`() { fun `assert ownership`() {
val aliceRootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name) val aliceRootKey = Crypto.generateKeyPair()
val aliceTxCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, aliceRootCertAndKey) val aliceRootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, aliceRootKey)
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCertAndKey, aliceTxCertAndKey.certificate, false).certPath val aliceTxKey = Crypto.generateKeyPair()
val bobRootCertAndKey = X509Utilities.createSelfSignedCACert(BOB.name) val aliceTxCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, aliceRootCert, aliceRootKey, ALICE.name, aliceTxKey.public)
val bobTxCertAndKey = X509Utilities.createIntermediateCert(BOB.name, bobRootCertAndKey) val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCert, aliceTxCert, revocationEnabled = false)
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)
service.registerPath(aliceRootCertAndKey.certificate, anonymousAlice, aliceCertPath) val bobRootKey = Crypto.generateKeyPair()
service.registerPath(bobRootCertAndKey.certificate, anonymousBob, bobCertPath) 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 // Verify that paths are verified
service.assertOwnership(alice, anonymousAlice) 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.any
import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.mock
import net.corda.core.crypto.KeyStoreUtilities import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.X509Utilities
import net.corda.core.exists import net.corda.core.exists
import net.corda.core.utilities.ALICE import net.corda.core.utilities.ALICE
import net.corda.testing.TestNodeConfiguration import net.corda.testing.TestNodeConfiguration
import net.corda.testing.getTestX509Name import net.corda.testing.getTestX509Name
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.cert.X509CertificateHolder
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
@ -31,7 +29,7 @@ class NetworkRegistrationHelperTest {
"CORDA_INTERMEDIATE_CA", "CORDA_INTERMEDIATE_CA",
"CORDA_ROOT_CA") "CORDA_ROOT_CA")
.map { getTestX509Name(it) } .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() .toTypedArray()
val certService: NetworkRegistrationService = mock { val certService: NetworkRegistrationService = mock {
@ -44,32 +42,45 @@ class NetworkRegistrationHelperTest {
myLegalName = ALICE.name, myLegalName = ALICE.name,
networkMapService = null) networkMapService = null)
assertFalse(config.keyStoreFile.exists()) assertFalse(config.nodeKeystore.exists())
assertFalse(config.sslKeystore.exists())
assertFalse(config.trustStoreFile.exists()) assertFalse(config.trustStoreFile.exists())
NetworkRegistrationHelper(config, certService).buildKeystore() NetworkRegistrationHelper(config, certService).buildKeystore()
assertTrue(config.keyStoreFile.exists()) assertTrue(config.nodeKeystore.exists())
assertTrue(config.sslKeystore.exists())
assertTrue(config.trustStoreFile.exists()) assertTrue(config.trustStoreFile.exists())
KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword).run { val nodeKeystore = KeyStoreUtilities.loadKeyStore(config.nodeKeystore, config.keyStorePassword)
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY)) val sslKeystore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword)
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
assertEquals(3, certificateChain.size)
nodeKeystore.run {
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_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))
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 { sslKeystore.run {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY)) 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_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY))
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA)) 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.** * **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. * **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. * **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. * **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) * **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. Please see docs/build/html/running-the-demos.html to learn how to use this demo.

View File

@ -41,7 +41,7 @@ publishing {
publications { publications {
jarAndSources(MavenPublication) { jarAndSources(MavenPublication) {
from components.java from components.java
artifactId 'raftnotarydemo' artifactId 'notarydemo'
artifact sourceJar artifact sourceJar
artifact javadocJar artifact javadocJar
@ -57,6 +57,10 @@ task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
definitionClass = 'net.corda.notarydemo.RaftNotaryCordform' definitionClass = 'net.corda.notarydemo.RaftNotaryCordform'
} }
task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
definitionClass = 'net.corda.notarydemo.BFTNotaryCordform'
}
task notarise(type: JavaExec) { task notarise(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
main = 'net.corda.notarydemo.NotariseKt' 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.CordformDefinition
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
fun CordformDefinition.node(configure: CordformNode.() -> Unit) = addNode { cordformNode -> cordformNode.configure() }
fun CordformDefinition.clean() { fun CordformDefinition.clean() {
System.err.println("Deleting: $driverDirectory") System.err.println("Deleting: $driverDirectory")
driverDirectory.toFile().deleteRecursively() 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 import net.corda.demorun.clean
fun main(args: Array<String>) { fun main(args: Array<String>) {
listOf(SingleNotaryCordform, RaftNotaryCordform).forEach { listOf(SingleNotaryCordform, RaftNotaryCordform, BFTNotaryCordform).forEach {
it.clean() it.clean()
} }
} }

View File

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

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.ALICE
import net.corda.core.utilities.BOB import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demorun.node
import net.corda.demorun.runNodes import net.corda.demorun.runNodes
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService 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.notarydemo.flows.RPCStartableNotaryFlowClient
import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformContext import net.corda.cordform.CordformContext
import net.corda.demorun.util.*
fun main(args: Array<String>) = SingleNotaryCordform.runNodes() 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) { object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", DUMMY_NOTARY.name) {
init { init {
node { node {
name(ALICE.name.toString()) name(ALICE.name)
nearestCity("London")
p2pPort(10002) p2pPort(10002)
rpcPort(10003) rpcPort(10003)
rpcUsers = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())).toMap()) rpcUsers(notaryDemoUser)
} }
node { node {
name(BOB.name.toString()) name(BOB.name)
nearestCity("New York")
p2pPort(10005) p2pPort(10005)
rpcPort(10006) rpcPort(10006)
} }
node { node {
name(DUMMY_NOTARY.name.toString()) name(DUMMY_NOTARY.name)
nearestCity("London")
advertisedServices = listOf(ServiceInfo(ValidatingNotaryService.type).toString())
p2pPort(10009) p2pPort(10009)
rpcPort(10010) 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:irs-demo'
include 'samples:network-visualiser' include 'samples:network-visualiser'
include 'samples:simm-valuation-demo' include 'samples:simm-valuation-demo'
include 'samples:raft-notary-demo' include 'samples:notary-demo'
include 'samples:bank-of-corda-demo' include 'samples:bank-of-corda-demo'
include 'cordform-common' include 'cordform-common'
include 'doorman' include 'doorman'

View File

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

View File

@ -1,7 +1,6 @@
package net.corda.testing.messaging package net.corda.testing.messaging
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import net.corda.core.crypto.X509Utilities
import net.corda.nodeapi.ArtemisMessagingComponent import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.ConnectionDirection
@ -37,6 +36,10 @@ class SimpleMQClient(val target: HostAndPort,
fun createMessage(): ClientMessage = session.createMessage(false) fun createMessage(): ClientMessage = session.createMessage(false)
fun stop() { 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 com.google.common.util.concurrent.ListenableFuture
import net.corda.core.* import net.corda.core.*
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.appendToCommonName
import net.corda.core.crypto.commonName import net.corda.core.crypto.commonName
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.DUMMY_MAP import net.corda.core.utilities.DUMMY_MAP
import net.corda.core.utilities.WHITESPACE
import net.corda.node.driver.addressMustNotBeBound import net.corda.node.driver.addressMustNotBeBound
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.ConfigHelper
@ -107,7 +109,7 @@ abstract class NodeBasedTest {
clusterSize: Int, clusterSize: Int,
serviceType: ServiceType = RaftValidatingNotaryService.type): ListenableFuture<List<Node>> { serviceType: ServiceType = RaftValidatingNotaryService.type): ListenableFuture<List<Node>> {
ServiceIdentityGenerator.generateToDisk( ServiceIdentityGenerator.generateToDisk(
(0 until clusterSize).map { tempFolder.root.toPath() / "${notaryName.commonName}-$it" }, (0 until clusterSize).map { baseDirectory(notaryName.appendToCommonName("-$it")) },
serviceType.id, serviceType.id,
notaryName) 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, private fun startNodeInternal(legalName: X500Name,
platformVersion: Int, platformVersion: Int,
advertisedServices: Set<ServiceInfo>, advertisedServices: Set<ServiceInfo>,
rpcUsers: List<User>, rpcUsers: List<User>,
configOverrides: Map<String, Any>): Node { configOverrides: Map<String, Any>): Node {
val baseDirectory = (tempFolder.root.toPath() / legalName.commonName).createDirectories() val baseDirectory = baseDirectory(legalName).createDirectories()
val localPort = getFreeLocalPorts("localhost", 2) val localPort = getFreeLocalPorts("localhost", 2)
val config = ConfigHelper.loadConfig( val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory, baseDirectory = baseDirectory,

View File

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

View File

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