mirror of
https://github.com/corda/corda.git
synced 2025-01-27 06:39:38 +00:00
Merge commit '246de55433f747707b2d0dd6299437c664ea933d' into mike-enterprise-remerge
Includes API updates to the doorman code.
This commit is contained in:
commit
782d4bd731
8
.gitignore
vendored
8
.gitignore
vendored
@ -32,7 +32,7 @@ lib/dokka.jar
|
||||
.idea/libraries
|
||||
.idea/shelf
|
||||
.idea/dataSources
|
||||
/gradle-plugins/.idea
|
||||
/gradle-plugins/.idea/
|
||||
|
||||
# Include the -parameters compiler option by default in IntelliJ required for serialization.
|
||||
!.idea/compiler.xml
|
||||
@ -84,8 +84,10 @@ crashlytics-build.properties
|
||||
docs/virtualenv/
|
||||
|
||||
# bft-smart
|
||||
node/bft-smart-config/currentView
|
||||
node/config/currentView
|
||||
config/currentView
|
||||
|
||||
# vim
|
||||
*.swp
|
||||
|
||||
# Files you may find useful to have in your working directory.
|
||||
PLAN
|
||||
|
6
.idea/compiler.xml
generated
6
.idea/compiler.xml
generated
@ -61,10 +61,10 @@
|
||||
<module name="node_integrationTest" target="1.8" />
|
||||
<module name="node_main" target="1.8" />
|
||||
<module name="node_test" target="1.8" />
|
||||
<module name="notary-demo_main" target="1.8" />
|
||||
<module name="notary-demo_test" target="1.8" />
|
||||
<module name="quasar-hook_main" target="1.8" />
|
||||
<module name="quasar-hook_test" target="1.8" />
|
||||
<module name="raft-notary-demo_main" target="1.8" />
|
||||
<module name="raft-notary-demo_test" target="1.8" />
|
||||
<module name="rpc_integrationTest" target="1.8" />
|
||||
<module name="rpc_main" target="1.8" />
|
||||
<module name="rpc_smokeTest" target="1.8" />
|
||||
@ -98,4 +98,4 @@
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
|
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=0.12.1
|
||||
gradlePluginsVersion=0.12.2
|
||||
kotlinVersion=1.1.2
|
||||
guavaVersion=21.0
|
||||
bouncycastleVersion=1.56
|
||||
|
@ -25,7 +25,8 @@ public class CordformNode {
|
||||
public List<String> advertisedServices = emptyList();
|
||||
|
||||
/**
|
||||
* If running a distributed notary, a list of node addresses for joining the Raft cluster
|
||||
* If running a Raft notary cluster, the address of at least one node in the cluster, or leave blank to start a new cluster.
|
||||
* If running a BFT notary cluster, the addresses of all nodes in the cluster.
|
||||
*/
|
||||
public List<String> notaryClusterAddresses = emptyList();
|
||||
/**
|
||||
@ -82,11 +83,18 @@ public class CordformNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the port which to bind the Copycat (Raft) node to
|
||||
* Set the port which to bind the Copycat (Raft) node to.
|
||||
*
|
||||
* @param notaryPort The Raft port.
|
||||
*/
|
||||
public void notaryNodePort(Integer notaryPort) {
|
||||
config = config.withValue("notaryNodeAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + notaryPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The (0-based) BFT replica ID.
|
||||
*/
|
||||
public void bftReplicaId(Integer id) {
|
||||
config = config.withValue("bftReplicaId", ConfigValueFactory.fromAnyRef(id));
|
||||
}
|
||||
}
|
||||
|
@ -110,8 +110,9 @@ infix fun <T> ListenableFuture<T>.failure(body: (Throwable) -> Unit): Listenable
|
||||
infix fun <F, T> ListenableFuture<F>.map(mapper: (F) -> T): ListenableFuture<T> = Futures.transform(this, { (mapper as (F?) -> T)(it) })
|
||||
infix fun <F, T> ListenableFuture<F>.flatMap(mapper: (F) -> ListenableFuture<T>): ListenableFuture<T> = Futures.transformAsync(this) { mapper(it!!) }
|
||||
|
||||
inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R) = run {
|
||||
val iterator = iterator()
|
||||
inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R) = mapToArray(transform, iterator(), size)
|
||||
inline fun <reified R> IntProgression.mapToArray(transform: (Int) -> R) = mapToArray(transform, iterator(), 1 + (last - first) / step)
|
||||
inline fun <T, reified R> mapToArray(transform: (T) -> R, iterator: Iterator<T>, size: Int) = run {
|
||||
var expected = 0
|
||||
Array(size) {
|
||||
expected++ == it || throw UnsupportedOperationException("Array constructor is non-sequential!")
|
||||
|
@ -14,7 +14,10 @@ import org.bouncycastle.asn1.bc.BCObjectIdentifiers
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
|
||||
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
@ -558,26 +561,26 @@ object Crypto {
|
||||
/**
|
||||
* Use bouncy castle utilities to sign completed X509 certificate with CA cert private key.
|
||||
*/
|
||||
fun createCertificate(issuer: X500Name, issuerKeyPair: KeyPair,
|
||||
fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair,
|
||||
subject: X500Name, subjectPublicKey: PublicKey,
|
||||
keyUsage: KeyUsage, purposes: List<KeyPurposeId>,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
pathLength: Int? = null, subjectAlternativeName: List<GeneralName>? = null): X509Certificate {
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
|
||||
val signatureScheme = findSignatureScheme(issuerKeyPair.private)
|
||||
val provider = providerMap[signatureScheme.providerName]
|
||||
val serial = BigInteger.valueOf(random63BitValue())
|
||||
val keyPurposes = DERSequence(ASN1EncodableVector().apply { purposes.forEach { add(it) } })
|
||||
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
|
||||
|
||||
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
|
||||
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectPublicKey.encoded)))
|
||||
.addExtension(Extension.basicConstraints, pathLength != null, if (pathLength == null) BasicConstraints(false) else BasicConstraints(pathLength))
|
||||
.addExtension(Extension.keyUsage, false, keyUsage)
|
||||
.addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA))
|
||||
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
|
||||
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
|
||||
|
||||
if (subjectAlternativeName != null && subjectAlternativeName.isNotEmpty()) {
|
||||
builder.addExtension(Extension.subjectAlternativeName, false, DERSequence(subjectAlternativeName.toTypedArray()))
|
||||
if (nameConstraints != null) {
|
||||
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
|
||||
}
|
||||
|
||||
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
|
||||
return JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer)).apply {
|
||||
checkValidity(Date())
|
||||
|
@ -5,23 +5,19 @@ import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.X500NameBuilder
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.KeyPurposeId
|
||||
import org.bouncycastle.asn1.x509.KeyUsage
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.util.IPAddress
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.InputStream
|
||||
import java.net.InetAddress
|
||||
import java.nio.file.Path
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStore
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
import java.security.cert.Certificate
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
@ -31,20 +27,12 @@ object X509Utilities {
|
||||
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
|
||||
|
||||
// Aliases for private keys and certificates.
|
||||
val CORDA_ROOT_CA_PRIVATE_KEY = "cordarootcaprivatekey"
|
||||
val CORDA_ROOT_CA = "cordarootca"
|
||||
val CORDA_INTERMEDIATE_CA_PRIVATE_KEY = "cordaintermediatecaprivatekey"
|
||||
val CORDA_INTERMEDIATE_CA = "cordaintermediateca"
|
||||
val CORDA_CLIENT_CA_PRIVATE_KEY = "cordaclientcaprivatekey"
|
||||
val CORDA_CLIENT_TLS = "cordaclienttls"
|
||||
val CORDA_CLIENT_CA = "cordaclientca"
|
||||
|
||||
private val CA_KEY_USAGE = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign)
|
||||
private val CLIENT_KEY_USAGE = KeyUsage(KeyUsage.digitalSignature)
|
||||
private val CA_KEY_PURPOSES = listOf(KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage)
|
||||
private val CLIENT_KEY_PURPOSES = listOf(KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth)
|
||||
|
||||
private val DEFAULT_VALIDITY_WINDOW = Pair(Duration.ofMillis(0), Duration.ofDays(365 * 10))
|
||||
|
||||
/**
|
||||
* Helper function to return the latest out of an instant and an optional date.
|
||||
*/
|
||||
@ -110,101 +98,59 @@ object X509Utilities {
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a de novo root self-signed X509 v3 CA cert and [KeyPair].
|
||||
* @param subject the cert Subject will be populated with the domain string.
|
||||
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return A data class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates.
|
||||
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates.
|
||||
* Create a de novo root self-signed X509 v3 CA cert.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACert(subject: X500Name,
|
||||
keyPair: KeyPair,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
|
||||
fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
|
||||
val cert = Crypto.createCertificate(subject, keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 2)
|
||||
return CertificateAndKeyPair(cert, keyPair)
|
||||
val cert = Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
|
||||
return cert
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair
|
||||
= createSelfSignedCACert(subject, generateKeyPair(signatureScheme), validityWindow)
|
||||
|
||||
/**
|
||||
* Create a de novo root intermediate X509 v3 CA cert and KeyPair.
|
||||
* Create a X509 v3 cert.
|
||||
* @param issuerCertificate The Public certificate of the root CA above this used to sign it.
|
||||
* @param issuerKeyPair The KeyPair of the root CA above this used to sign it.
|
||||
* @param subject subject of the generated certificate.
|
||||
* @param ca The Public certificate and KeyPair of the root CA certificate above this used to sign it.
|
||||
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
|
||||
* @param subjectPublicKey subject 's public key.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates.
|
||||
* Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createIntermediateCert(subject: X500Name,
|
||||
ca: CertificateAndKeyPair,
|
||||
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
|
||||
val keyPair = generateKeyPair(signatureScheme)
|
||||
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate)
|
||||
val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 1)
|
||||
return CertificateAndKeyPair(cert, keyPair)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an X509v3 certificate suitable for use in TLS roles.
|
||||
* @param subject The contents to put in the subject field of the certificate.
|
||||
* @param publicKey The PublicKey to be wrapped in the certificate.
|
||||
* @param ca The Public certificate and KeyPair of the parent CA that will sign this certificate.
|
||||
* @param subjectAlternativeNameDomains A set of alternate DNS names to be supported by the certificate during validation of the TLS handshakes.
|
||||
* @param subjectAlternativeNameIps A set of alternate IP addresses to be supported by the certificate during validation of the TLS handshakes.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return The generated X509Certificate suitable for use as a Server/Client certificate in TLS.
|
||||
* This certificate is not marked as a CA cert to be similar in nature to commercial certificates.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createTlsServerCert(subject: X500Name, publicKey: PublicKey,
|
||||
ca: CertificateAndKeyPair,
|
||||
subjectAlternativeNameDomains: List<String>,
|
||||
subjectAlternativeNameIps: List<String>,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
|
||||
|
||||
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate)
|
||||
val dnsNames = subjectAlternativeNameDomains.map { GeneralName(GeneralName.dNSName, it) }
|
||||
val ipAddresses = subjectAlternativeNameIps.filter {
|
||||
IPAddress.isValidIPv6WithNetmask(it) || IPAddress.isValidIPv6(it) || IPAddress.isValidIPv4WithNetmask(it) || IPAddress.isValidIPv4(it)
|
||||
}.map { GeneralName(GeneralName.iPAddress, it) }
|
||||
return Crypto.createCertificate(issuer, ca.keyPair, subject, publicKey, CLIENT_KEY_USAGE, CLIENT_KEY_PURPOSES, window, subjectAlternativeName = dnsNames + ipAddresses)
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuerCertificate: X509Certificate, issuerKeyPair: KeyPair,
|
||||
subject: X500Name, subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
|
||||
val cert = Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
|
||||
return cert
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a certificate path from a trusted root certificate to a target certificate. This will always return a path
|
||||
* directly from the root to the target, with no intermediate certificates (presuming that path is valid).
|
||||
* directly from the target to the root.
|
||||
*
|
||||
* @param rootCertAndKey trusted root certificate that will be the start of the path.
|
||||
* @param targetCertAndKey certificate the path ends at.
|
||||
* @param trustedRoot trusted root certificate that will be the start of the path.
|
||||
* @param certificates certificates in the path.
|
||||
* @param revocationEnabled whether revocation of certificates in the path should be checked.
|
||||
*/
|
||||
fun createCertificatePath(rootCertAndKey: CertificateAndKeyPair,
|
||||
targetCertAndKey: X509Certificate,
|
||||
revocationEnabled: Boolean): CertPathBuilderResult {
|
||||
val intermediateCertificates = setOf(targetCertAndKey)
|
||||
val certStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(intermediateCertificates))
|
||||
val certPathFactory = CertPathBuilder.getInstance("PKIX")
|
||||
val trustAnchor = TrustAnchor(rootCertAndKey.certificate, null)
|
||||
val certPathParameters = try {
|
||||
PKIXBuilderParameters(setOf(trustAnchor), X509CertSelector().apply {
|
||||
certificate = targetCertAndKey
|
||||
})
|
||||
} catch (ex: InvalidAlgorithmParameterException) {
|
||||
throw RuntimeException(ex)
|
||||
}.apply {
|
||||
addCertStore(certStore)
|
||||
isRevocationEnabled = revocationEnabled
|
||||
}
|
||||
return certPathFactory.build(certPathParameters)
|
||||
fun createCertificatePath(trustedRoot: X509Certificate, vararg certificates: X509Certificate, revocationEnabled: Boolean): CertPath {
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
params.isRevocationEnabled = revocationEnabled
|
||||
return certFactory.generateCertPath(certificates.toList())
|
||||
}
|
||||
|
||||
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
|
||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(certificates.toList())
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
pathValidator.validate(certPath, params)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,7 +183,8 @@ object X509Utilities {
|
||||
|
||||
/**
|
||||
* An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine.
|
||||
* @param keyStoreFilePath KeyStore path to save output to.
|
||||
* @param sslKeyStorePath KeyStore path to save ssl key and cert to.
|
||||
* @param clientCAKeystorePath KeyStore path to save client CA key and cert to.
|
||||
* @param storePassword access password for KeyStore.
|
||||
* @param keyPassword PrivateKey access password for the generated keys.
|
||||
* It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same.
|
||||
@ -245,57 +192,66 @@ object X509Utilities {
|
||||
* @param caKeyPassword password to unlock private keys in the CA KeyStore.
|
||||
* @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications.
|
||||
*/
|
||||
fun createKeystoreForSSL(keyStoreFilePath: Path,
|
||||
storePassword: String,
|
||||
keyPassword: String,
|
||||
caKeyStore: KeyStore,
|
||||
caKeyPassword: String,
|
||||
commonName: X500Name,
|
||||
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME): KeyStore {
|
||||
fun createKeystoreForCordaNode(sslKeyStorePath: Path,
|
||||
clientCAKeystorePath: Path,
|
||||
storePassword: String,
|
||||
keyPassword: String,
|
||||
caKeyStore: KeyStore,
|
||||
caKeyPassword: String,
|
||||
legalName: X500Name,
|
||||
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) {
|
||||
|
||||
val rootCA = caKeyStore.getCertificateAndKeyPair(CORDA_ROOT_CA_PRIVATE_KEY, caKeyPassword)
|
||||
val intermediateCA = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, caKeyPassword)
|
||||
val rootCACert = caKeyStore.getX509Certificate(CORDA_ROOT_CA)
|
||||
val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA, caKeyPassword)
|
||||
|
||||
val serverKey = generateKeyPair(signatureScheme)
|
||||
val host = InetAddress.getLocalHost()
|
||||
val serverCert = createTlsServerCert(commonName, serverKey.public, intermediateCA, listOf(host.hostName), listOf(host.hostAddress))
|
||||
val clientKey = generateKeyPair(signatureScheme)
|
||||
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf())
|
||||
val clientCACert = createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, legalName, clientKey.public, nameConstraints = nameConstraints)
|
||||
|
||||
val tlsKey = generateKeyPair(signatureScheme)
|
||||
val clientTLSCert = createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public)
|
||||
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
|
||||
|
||||
keyStore.addOrReplaceKey(
|
||||
CORDA_CLIENT_CA_PRIVATE_KEY,
|
||||
serverKey.private,
|
||||
val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(clientCAKeystorePath, storePassword)
|
||||
clientCAKeystore.addOrReplaceKey(
|
||||
CORDA_CLIENT_CA,
|
||||
clientKey.private,
|
||||
keyPass,
|
||||
arrayOf(serverCert, intermediateCA.certificate, rootCA.certificate))
|
||||
keyStore.addOrReplaceCertificate(CORDA_CLIENT_CA, serverCert)
|
||||
keyStore.save(keyStoreFilePath, storePassword)
|
||||
return keyStore
|
||||
arrayOf(clientCACert, intermediateCACert, rootCACert))
|
||||
clientCAKeystore.save(clientCAKeystorePath, storePassword)
|
||||
|
||||
val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeyStorePath, storePassword)
|
||||
tlsKeystore.addOrReplaceKey(
|
||||
CORDA_CLIENT_TLS,
|
||||
tlsKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))
|
||||
tlsKeystore.save(sslKeyStorePath, storePassword)
|
||||
}
|
||||
|
||||
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) = Crypto.createCertificateSigningRequest(subject, keyPair, signatureScheme)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present, this throws an
|
||||
* exception.
|
||||
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
|
||||
* adds one.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
|
||||
*
|
||||
* @param mutator a function to generate the new value from the previous one.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
|
||||
val builder = X500NameBuilder(BCStyle.INSTANCE)
|
||||
var matched = false
|
||||
@ -319,6 +275,7 @@ private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500N
|
||||
val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
|
||||
val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
|
||||
val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
|
||||
val X509Certificate.subject: X500Name get() = X509CertificateHolder(encoded).subject
|
||||
|
||||
class CertificateStream(val input: InputStream) {
|
||||
private val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
@ -327,3 +284,11 @@ class CertificateStream(val input: InputStream) {
|
||||
}
|
||||
|
||||
data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair)
|
||||
|
||||
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
|
||||
ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
CLIENT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
TLS(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.keyAgreement), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false),
|
||||
IDENTITY(KeyUsage(KeyUsage.digitalSignature), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false)
|
||||
}
|
26
core/src/main/kotlin/net/corda/core/internal/ShutdownHook.kt
Normal file
26
core/src/main/kotlin/net/corda/core/internal/ShutdownHook.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
|
||||
import net.corda.core.crypto.Crypto.generateKeyPair
|
||||
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
|
||||
import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate
|
||||
import net.corda.core.div
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.getTestX509Name
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -33,61 +36,53 @@ class X509UtilitiesTest {
|
||||
|
||||
@Test
|
||||
fun `create valid self-signed CA certificate`() {
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test Cert"))
|
||||
assertTrue { caCertAndKey.certificate.subjectDN.name.contains("CN=Test Cert") } // using our subject common name
|
||||
assertEquals(caCertAndKey.certificate.issuerDN, caCertAndKey.certificate.subjectDN) //self-signed
|
||||
caCertAndKey.certificate.checkValidity(Date()) // throws on verification problems
|
||||
caCertAndKey.certificate.verify(caCertAndKey.keyPair.public) // throws on verification problems
|
||||
assertTrue { caCertAndKey.certificate.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertTrue { caCertAndKey.certificate.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate
|
||||
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
|
||||
assertTrue { caCert.subjectDN.name.contains("CN=Test Cert") } // using our subject common name
|
||||
assertEquals(caCert.issuerDN, caCert.subjectDN) //self-signed
|
||||
caCert.checkValidity(Date()) // throws on verification problems
|
||||
caCert.verify(caKey.public) // throws on verification problems
|
||||
assertTrue { caCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertTrue { caCert.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `load and save a PEM file certificate`() {
|
||||
val tmpCertificateFile = tempFile("cacert.pem")
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test Cert"))
|
||||
X509Utilities.saveCertificateAsPEMFile(caCertAndKey.certificate, tmpCertificateFile)
|
||||
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
|
||||
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
|
||||
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
||||
assertEquals(caCertAndKey.certificate, readCertificate)
|
||||
assertEquals(caCert, readCertificate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create valid server certificate chain`() {
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test CA Cert"))
|
||||
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = createSelfSignedCACertificate(getTestX509Name("Test CA Cert"), caKey)
|
||||
val subjectDN = getTestX509Name("Server Cert")
|
||||
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val serverCert = X509Utilities.createTlsServerCert(subjectDN, keyPair.public, caCertAndKey, listOf("alias name"), listOf("10.0.0.54"))
|
||||
val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subjectDN, keyPair.public)
|
||||
assertTrue { serverCert.subjectDN.name.contains("CN=Server Cert") } // using our subject common name
|
||||
assertEquals(caCertAndKey.certificate.issuerDN, serverCert.issuerDN) // Issued by our CA cert
|
||||
assertEquals(caCert.issuerDN, serverCert.issuerDN) // Issued by our CA cert
|
||||
serverCert.checkValidity(Date()) // throws on verification problems
|
||||
serverCert.verify(caCertAndKey.keyPair.public) // throws on verification problems
|
||||
serverCert.verify(caKey.public) // throws on verification problems
|
||||
assertFalse { serverCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertTrue { serverCert.basicConstraints == -1 } // This returns the signing path length should be -1 for non-CA certificate
|
||||
assertEquals(2, serverCert.subjectAlternativeNames.size)
|
||||
var foundAliasDnsName = false
|
||||
for (entry in serverCert.subjectAlternativeNames) {
|
||||
val typeId = entry[0] as Int
|
||||
val value = entry[1] as String
|
||||
if (typeId == GeneralName.iPAddress) {
|
||||
assertEquals("10.0.0.54", value)
|
||||
} else if (value == "alias name") {
|
||||
foundAliasDnsName = true
|
||||
}
|
||||
}
|
||||
assertTrue(foundAliasDnsName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storing EdDSA key in java keystore`() {
|
||||
val tmpKeyStore = tempFile("keystore.jks")
|
||||
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), Crypto.EDDSA_ED25519_SHA512)
|
||||
val keyPair = generateKeyPair(EDDSA_ED25519_SHA512)
|
||||
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
|
||||
|
||||
assertEquals(selfSignCert.certificate.publicKey, selfSignCert.keyPair.public)
|
||||
assertEquals(selfSignCert.publicKey, keyPair.public)
|
||||
|
||||
// Save the EdDSA private key with self sign cert in the keystore.
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
|
||||
keyStore.setKeyEntry("Key", selfSignCert.keyPair.private, "password".toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), arrayOf(selfSignCert))
|
||||
keyStore.save(tmpKeyStore, "keystorepass")
|
||||
|
||||
// Load the keystore from file and make sure keys are intact.
|
||||
@ -97,20 +92,21 @@ class X509UtilitiesTest {
|
||||
|
||||
assertNotNull(pubKey)
|
||||
assertNotNull(privateKey)
|
||||
assertEquals(selfSignCert.keyPair.public, pubKey)
|
||||
assertEquals(selfSignCert.keyPair.private, privateKey)
|
||||
assertEquals(keyPair.public, pubKey)
|
||||
assertEquals(keyPair.private, privateKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `signing EdDSA key with EcDSA certificate`() {
|
||||
val tmpKeyStore = tempFile("keystore.jks")
|
||||
val ecDSACert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"))
|
||||
val edDSAKeypair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
|
||||
val edDSACert = X509Utilities.createTlsServerCert(X500Name("CN=TestEdDSA"), edDSAKeypair.public, ecDSACert, listOf("alias name"), listOf("10.0.0.54"))
|
||||
val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val ecDSACert = createSelfSignedCACertificate(X500Name("CN=Test"), ecDSAKey)
|
||||
val edDSAKeypair = generateKeyPair(EDDSA_ED25519_SHA512)
|
||||
val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public)
|
||||
|
||||
// Save the EdDSA private key with cert chains.
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
|
||||
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert.certificate, edDSACert))
|
||||
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert, edDSACert))
|
||||
keyStore.save(tmpKeyStore, "keystorepass")
|
||||
|
||||
// Load the keystore from file and make sure keys are intact.
|
||||
@ -138,8 +134,8 @@ class X509UtilitiesTest {
|
||||
// Load back generated root CA Cert and private key from keystore and check against copy in truststore
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass")
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
|
||||
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY) as X509Certificate
|
||||
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey
|
||||
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
|
||||
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA, "keypass".toCharArray()) as PrivateKey
|
||||
val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
|
||||
assertEquals(rootCaCert, rootCaFromTrustStore)
|
||||
rootCaCert.checkValidity(Date())
|
||||
@ -147,24 +143,25 @@ class X509UtilitiesTest {
|
||||
|
||||
// Now sign something with private key and verify against certificate public key
|
||||
val testData = "12345".toByteArray()
|
||||
val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
|
||||
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
|
||||
val caSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
|
||||
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
|
||||
|
||||
// Load back generated intermediate CA Cert and private key
|
||||
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) as X509Certificate
|
||||
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey
|
||||
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA) as X509Certificate
|
||||
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA, "keypass".toCharArray()) as PrivateKey
|
||||
intermediateCaCert.checkValidity(Date())
|
||||
intermediateCaCert.verify(rootCaCert.publicKey)
|
||||
|
||||
// Now sign something with private key and verify against certificate public key
|
||||
val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
|
||||
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
|
||||
val intermediateSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
|
||||
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create server certificate in keystore for SSL`() {
|
||||
val tmpCAKeyStore = tempFile("keystore.jks")
|
||||
val tmpTrustStore = tempFile("truststore.jks")
|
||||
val tmpSSLKeyStore = tempFile("sslkeystore.jks")
|
||||
val tmpServerKeyStore = tempFile("serverkeystore.jks")
|
||||
|
||||
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
|
||||
@ -176,30 +173,39 @@ class X509UtilitiesTest {
|
||||
|
||||
// Load signing intermediate CA cert
|
||||
val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass")
|
||||
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "cakeypass")
|
||||
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cakeypass")
|
||||
|
||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||
X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
|
||||
// Load back server certificate
|
||||
val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass")
|
||||
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY, "serverkeypass")
|
||||
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass")
|
||||
|
||||
serverCertAndKey.certificate.checkValidity(Date())
|
||||
serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey)
|
||||
|
||||
assertTrue { serverCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) }
|
||||
|
||||
// Load back server certificate
|
||||
val sslKeyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass")
|
||||
val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass")
|
||||
|
||||
sslCertAndKey.certificate.checkValidity(Date())
|
||||
sslCertAndKey.certificate.verify(serverCertAndKey.certificate.publicKey)
|
||||
|
||||
assertTrue { sslCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) }
|
||||
// Now sign something with private key and verify against certificate public key
|
||||
val testData = "123456".toByteArray()
|
||||
val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
|
||||
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
|
||||
val signature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
|
||||
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create server cert and use in SSL socket`() {
|
||||
val tmpCAKeyStore = tempFile("keystore.jks")
|
||||
val tmpTrustStore = tempFile("truststore.jks")
|
||||
val tmpSSLKeyStore = tempFile("sslkeystore.jks")
|
||||
val tmpServerKeyStore = tempFile("serverkeystore.jks")
|
||||
|
||||
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
|
||||
@ -210,7 +216,8 @@ class X509UtilitiesTest {
|
||||
"trustpass")
|
||||
|
||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||
val keyStore = X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass")
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
|
||||
|
||||
val context = SSLContext.getInstance("TLS")
|
||||
@ -235,7 +242,7 @@ class X509UtilitiesTest {
|
||||
arrayOf("TLSv1.2"))
|
||||
serverParams.wantClientAuth = true
|
||||
serverParams.needClientAuth = true
|
||||
serverParams.endpointIdentificationAlgorithm = "HTTPS" // enable hostname checking
|
||||
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||
serverSocket.sslParameters = serverParams
|
||||
serverSocket.useClientMode = false
|
||||
|
||||
@ -247,7 +254,7 @@ class X509UtilitiesTest {
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"),
|
||||
arrayOf("TLSv1.2"))
|
||||
clientParams.endpointIdentificationAlgorithm = "HTTPS" // enable hostname checking
|
||||
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||
clientSocket.sslParameters = clientParams
|
||||
clientSocket.useClientMode = true
|
||||
// We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind
|
||||
@ -284,8 +291,7 @@ class X509UtilitiesTest {
|
||||
val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
|
||||
val x500name = X500Name(peerX500Principal.name)
|
||||
assertEquals(MEGA_CORP.name, x500name)
|
||||
|
||||
|
||||
X509Utilities.validateCertificateChain(trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), *peerChain)
|
||||
val output = DataOutputStream(clientSocket.outputStream)
|
||||
output.writeUTF("Hello World")
|
||||
var timeout = 0
|
||||
@ -324,36 +330,40 @@ class X509UtilitiesTest {
|
||||
trustStoreFilePath: Path,
|
||||
trustStorePassword: String
|
||||
): KeyStore {
|
||||
val rootCA = X509Utilities.createSelfSignedCACert(X509Utilities.getDevX509Name("Corda Node Root CA"))
|
||||
val intermediateCA = X509Utilities.createIntermediateCert(X509Utilities.getDevX509Name("Corda Node Intermediate CA"), rootCA)
|
||||
val rootCAKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Node Root CA"), rootCAKey)
|
||||
|
||||
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X509Utilities.getDevX509Name("Corda Node Intermediate CA"), intermediateCAKeyPair.public)
|
||||
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
|
||||
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, rootCA.keyPair.private, keyPass, arrayOf(rootCA.certificate))
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(rootCACert))
|
||||
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY,
|
||||
intermediateCA.keyPair.private,
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA,
|
||||
intermediateCAKeyPair.private,
|
||||
keyPass,
|
||||
arrayOf(intermediateCA.certificate, rootCA.certificate))
|
||||
arrayOf(intermediateCACert, rootCACert))
|
||||
|
||||
keyStore.save(keyStoreFilePath, storePassword)
|
||||
|
||||
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword)
|
||||
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCA.certificate)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCA.certificate)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert)
|
||||
|
||||
trustStore.save(trustStoreFilePath, trustStorePassword)
|
||||
|
||||
return keyStore
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get correct private key type from Keystore`() {
|
||||
val keyPair = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), keyPair)
|
||||
val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert))
|
||||
|
||||
val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
|
||||
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")
|
||||
|
@ -143,7 +143,7 @@ class KryoTests {
|
||||
|
||||
@Test
|
||||
fun `serialize - deserialize X509Certififcate`() {
|
||||
val expected = X509Utilities.createSelfSignedCACert(ALICE.name).certificate
|
||||
val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
val serialized = expected.serialize(kryo).bytes
|
||||
val actual: X509Certificate = serialized.deserialize(kryo)
|
||||
assertEquals(expected, actual)
|
||||
@ -151,9 +151,10 @@ class KryoTests {
|
||||
|
||||
@Test
|
||||
fun `serialize - deserialize X509CertPath`() {
|
||||
val rootCA = X509Utilities.createSelfSignedCACert(ALICE.name)
|
||||
val certificate = X509Utilities.createTlsServerCert(BOB.name, BOB_PUBKEY, rootCA, emptyList(), emptyList())
|
||||
val expected = X509Utilities.createCertificatePath(rootCA, certificate, false).certPath
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY)
|
||||
val expected = X509Utilities.createCertificatePath(rootCACert, certificate, revocationEnabled = false)
|
||||
val serialized = expected.serialize(kryo).bytes
|
||||
val actual: CertPath = serialized.deserialize(kryo)
|
||||
assertEquals(expected, actual)
|
||||
|
@ -73,6 +73,11 @@ UNRELEASED
|
||||
point we will support the ability for a node to have multiple versions of the same flow registered, enabling backwards
|
||||
compatibility of CorDapp flows.
|
||||
|
||||
* The certificate hierarchy has been changed in order to allow corda node to sign keys with proper certificate chain.
|
||||
* The corda node will now be issued a restricted client CA for identity/transaction key signing.
|
||||
* TLS certificate are now stored in `sslkeystore.jks` and identity keys are stored in `nodekeystore.jks`
|
||||
.. warning:: The old keystore will need to be removed when upgrading to this version.
|
||||
|
||||
Milestone 11.1
|
||||
--------------
|
||||
|
||||
|
@ -107,33 +107,38 @@ To run from the command line in Windows:
|
||||
4. Run ``gradlew samples:attachment-demo:runSender`` in another terminal window to send the attachment. Now look at the other windows to
|
||||
see the output of the demo
|
||||
|
||||
Raft Notary demo
|
||||
----------------
|
||||
Notary demo
|
||||
-----------
|
||||
|
||||
This demo shows a party getting transactions notarised by a distributed `Raft <https://raft.github.io/>`_-based notary service.
|
||||
The demo will start three distributed notary nodes, and two counterparty nodes. One of the counterparties will generate transactions
|
||||
that transfer a self-issued asset to the other party and submit them for notarisation.
|
||||
This demo shows a party getting transactions notarised by either a single-node or a distributed notary service.
|
||||
All versions of the demo start two counterparty nodes.
|
||||
One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit them for notarisation.
|
||||
The `Raft <https://raft.github.io/>`_ version of the demo will start three distributed notary nodes.
|
||||
The `BFT SMaRt <https://bft-smart.github.io/library/>`_ version of the demo will start four distributed notary nodes.
|
||||
|
||||
The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary,
|
||||
every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement.
|
||||
In the BFT SMaRt distributed notary, three signatures are required.
|
||||
You will notice that successive transactions get signed by different members of the cluster (usually allocated in a random order).
|
||||
|
||||
To run from the command line in Unix:
|
||||
To run the Raft version of the demo from the command line in Unix:
|
||||
|
||||
1. Run ``./gradlew samples:raft-notary-demo:deployNodes``, which will create node directories with configs under ``samples/raft-notary-demo/build/nodes``.
|
||||
2. Run ``./samples/raft-notary-demo/build/nodes/runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||
1. Run ``./gradlew samples:notary-demo:deployNodesRaft``, which will create node directories with configs under ``samples/notary-demo/build/nodes``.
|
||||
2. Run ``./samples/notary-demo/build/nodes/runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||
Wait until a "Node started up and registered in ..." message appears on each of the terminals
|
||||
3. Run ``./gradlew samples:raft-notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
||||
3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
||||
In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys
|
||||
|
||||
To run from the command line in Windows:
|
||||
|
||||
1. Run ``gradlew samples:raft-notary-demo:deployNodes``, which will create node directories with configs under ``samples\raft-notary-demo\build\nodes``.
|
||||
2. Run ``samples\raft-notary-demo\build\nodes\runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||
1. Run ``gradlew samples:notary-demo:deployNodesRaft``, which will create node directories with configs under ``samples\notary-demo\build\nodes``.
|
||||
2. Run ``samples\notary-demo\build\nodes\runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||
Wait until a "Node started up and registered in ..." message appears on each of the terminals
|
||||
3. Run ``gradlew samples:raft-notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
||||
3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
||||
In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys
|
||||
|
||||
To run the BFT SMaRt notary demo, use ``deployNodesBFT`` instead of ``deployNodesRaft``. For a single notary node, use ``deployNodesSingle``.
|
||||
|
||||
Notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node.
|
||||
You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores
|
||||
by using the H2 web console:
|
||||
|
@ -11,15 +11,15 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.KeyStoreUtilities.loadKeyStore
|
||||
import net.corda.core.crypto.KeyStoreUtilities.loadOrCreateKeyStore
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY
|
||||
import net.corda.core.crypto.X509Utilities.createIntermediateCert
|
||||
import net.corda.core.crypto.X509Utilities.createTlsServerCert
|
||||
import net.corda.core.crypto.X509Utilities.createCertificate
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
@ -83,8 +83,7 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CertificateAnd
|
||||
for (id in storage.getApprovedRequestIds()) {
|
||||
storage.approveRequest(id) {
|
||||
val request = JcaPKCS10CertificationRequest(request)
|
||||
createTlsServerCert(request.subject, request.publicKey, caCertAndKey,
|
||||
if (ipAddress == hostName) listOf() else listOf(hostName), listOf(ipAddress))
|
||||
createCertificate(CertificateType.CLIENT_CA, caCertAndKey.certificate, caCertAndKey.keyPair, request.subject, request.publicKey, nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))), arrayOf()))
|
||||
}
|
||||
logger.info("Approved request $id")
|
||||
serverStatus.lastApprovalTime = Instant.now()
|
||||
@ -137,19 +136,20 @@ private fun DoormanParameters.generateRootKeyPair() {
|
||||
val rootStore = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword)
|
||||
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ")
|
||||
|
||||
if (rootStore.containsAlias(CORDA_ROOT_CA_PRIVATE_KEY)) {
|
||||
val oldKey = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA_PRIVATE_KEY).publicKey
|
||||
println("Key $CORDA_ROOT_CA_PRIVATE_KEY already exists in keystore, process will now terminate.")
|
||||
if (rootStore.containsAlias(CORDA_ROOT_CA)) {
|
||||
val oldKey = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA).publicKey
|
||||
println("Key $CORDA_ROOT_CA already exists in keystore, process will now terminate.")
|
||||
println(oldKey)
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name(CORDA_ROOT_CA))
|
||||
rootStore.addOrReplaceKey(CORDA_ROOT_CA_PRIVATE_KEY, selfSignCert.keyPair.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name(CORDA_ROOT_CA), selfSignKey)
|
||||
rootStore.addOrReplaceKey(CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert))
|
||||
rootStore.save(rootStorePath, rootKeystorePassword)
|
||||
|
||||
println("Root CA keypair and certificate stored in $rootStorePath.")
|
||||
println(loadKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA_PRIVATE_KEY).publicKey)
|
||||
println(loadKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA).publicKey)
|
||||
}
|
||||
|
||||
private fun DoormanParameters.generateCAKeyPair() {
|
||||
@ -159,7 +159,7 @@ private fun DoormanParameters.generateCAKeyPair() {
|
||||
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ")
|
||||
val rootKeyStore = loadKeyStore(rootStorePath, rootKeystorePassword)
|
||||
|
||||
val rootKeyAndCert = rootKeyStore.getCertificateAndKeyPair(rootPrivateKeyPassword, CORDA_ROOT_CA_PRIVATE_KEY)
|
||||
val rootKeyAndCert = rootKeyStore.getCertificateAndKeyPair(rootPrivateKeyPassword, CORDA_ROOT_CA)
|
||||
|
||||
val keystorePassword = keystorePassword ?: readPassword("Keystore Password: ")
|
||||
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ")
|
||||
@ -167,19 +167,20 @@ private fun DoormanParameters.generateCAKeyPair() {
|
||||
keystorePath.parent.createDirectories()
|
||||
val keyStore = loadOrCreateKeyStore(keystorePath, keystorePassword)
|
||||
|
||||
if (keyStore.containsAlias(CORDA_INTERMEDIATE_CA_PRIVATE_KEY)) {
|
||||
val oldKey = loadOrCreateKeyStore(keystorePath, rootKeystorePassword).getCertificate(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).publicKey
|
||||
println("Key $CORDA_INTERMEDIATE_CA_PRIVATE_KEY already exists in keystore, process will now terminate.")
|
||||
if (keyStore.containsAlias(CORDA_INTERMEDIATE_CA)) {
|
||||
val oldKey = loadOrCreateKeyStore(keystorePath, rootKeystorePassword).getCertificate(CORDA_INTERMEDIATE_CA).publicKey
|
||||
println("Key $CORDA_INTERMEDIATE_CA already exists in keystore, process will now terminate.")
|
||||
println(oldKey)
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
val intermediateKeyAndCert = createIntermediateCert(X500Name(CORDA_INTERMEDIATE_CA), rootKeyAndCert)
|
||||
keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, intermediateKeyAndCert.keyPair.private,
|
||||
caPrivateKeyPassword.toCharArray(), arrayOf(intermediateKeyAndCert.certificate, rootKeyAndCert.certificate))
|
||||
val intermediateKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCert = createCertificate(CertificateType.INTERMEDIATE_CA, rootKeyAndCert.certificate, rootKeyAndCert.keyPair, X500Name(CORDA_INTERMEDIATE_CA), intermediateKey.public)
|
||||
keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA, intermediateKey.private,
|
||||
caPrivateKeyPassword.toCharArray(), arrayOf(intermediateCert, rootKeyAndCert.certificate))
|
||||
keyStore.save(keystorePath, keystorePassword)
|
||||
println("Intermediate CA keypair and certificate stored in $keystorePath.")
|
||||
println(loadKeyStore(keystorePath, keystorePassword).getCertificate(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).publicKey)
|
||||
println(loadKeyStore(keystorePath, keystorePassword).getCertificate(CORDA_INTERMEDIATE_CA).publicKey)
|
||||
}
|
||||
|
||||
private fun DoormanParameters.startDoorman() {
|
||||
@ -189,8 +190,8 @@ private fun DoormanParameters.startDoorman() {
|
||||
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ")
|
||||
|
||||
val keystore = loadOrCreateKeyStore(keystorePath, keystorePassword)
|
||||
val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).last()
|
||||
val caCertAndKey = keystore.getCertificateAndKeyPair(caPrivateKeyPassword, CORDA_INTERMEDIATE_CA_PRIVATE_KEY)
|
||||
val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA).last()
|
||||
val caCertAndKey = keystore.getCertificateAndKeyPair(caPrivateKeyPassword, CORDA_INTERMEDIATE_CA)
|
||||
// Create DB connection.
|
||||
val (datasource, database) = configureDatabase(dataSourceProperties)
|
||||
|
||||
|
@ -5,10 +5,7 @@ import com.nhaarman.mockito_kotlin.*
|
||||
import com.r3.corda.doorman.persistence.CertificateResponse
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestData
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestStorage
|
||||
import net.corda.core.crypto.CertificateStream
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -29,12 +26,14 @@ import javax.ws.rs.core.MediaType
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DoormanServiceTest {
|
||||
private val rootCA = X509Utilities.createSelfSignedCACert(X500Name("CN=Corda Node Root CA,L=London"))
|
||||
private val intermediateCA = X509Utilities.createSelfSignedCACert(X500Name("CN=Corda Node Intermediate CA,L=London"))
|
||||
private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
private val rootCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Root CA,L=London"), rootCAKey)
|
||||
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
private val intermediateCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey)
|
||||
private lateinit var doormanServer: DoormanServer
|
||||
|
||||
private fun startSigningServer(storage: CertificationRequestStorage) {
|
||||
doormanServer = DoormanServer(HostAndPort.fromParts("localhost", 0), intermediateCA, rootCA.certificate, storage)
|
||||
doormanServer = DoormanServer(HostAndPort.fromParts("localhost", 0), CertificateAndKeyPair(intermediateCACert, intermediateCAKey), rootCACert, storage)
|
||||
doormanServer.start()
|
||||
}
|
||||
|
||||
@ -90,8 +89,7 @@ class DoormanServiceTest {
|
||||
|
||||
storage.approveRequest(id) {
|
||||
JcaPKCS10CertificationRequest(request).run {
|
||||
X509Utilities.createTlsServerCert(subject, publicKey, intermediateCA,
|
||||
if (ipAddress == hostName) listOf() else listOf(hostName), listOf(ipAddress))
|
||||
X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package com.r3.corda.doorman.internal.persistence
|
||||
import com.r3.corda.doorman.persistence.CertificateResponse
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestData
|
||||
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
|
||||
import net.corda.core.crypto.CertificateType
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
|
||||
@ -21,7 +22,8 @@ import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class DBCertificateRequestStorageTest {
|
||||
private val intermediateCA = X509Utilities.createSelfSignedCACert(X500Name("CN=Corda Node Intermediate CA"))
|
||||
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
private val intermediateCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Intermediate CA"), intermediateCAKey)
|
||||
private var closeDb: Closeable? = null
|
||||
private lateinit var storage: DBCertificateRequestStorage
|
||||
|
||||
@ -137,12 +139,12 @@ class DBCertificateRequestStorageTest {
|
||||
private fun approveRequest(requestId: String) {
|
||||
storage.approveRequest(requestId) {
|
||||
JcaPKCS10CertificationRequest(request).run {
|
||||
X509Utilities.createTlsServerCert(
|
||||
X509Utilities.createCertificate(
|
||||
CertificateType.TLS,
|
||||
intermediateCACert,
|
||||
intermediateCAKey,
|
||||
subject,
|
||||
publicKey,
|
||||
intermediateCA,
|
||||
if (ipAddress == hostName) listOf() else listOf(hostName),
|
||||
listOf(ipAddress))
|
||||
publicKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,8 +107,10 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
|
||||
*/
|
||||
fun checkStorePasswords() {
|
||||
val config = config ?: return
|
||||
config.keyStoreFile.read {
|
||||
KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray())
|
||||
arrayOf(config.sslKeystore, config.nodeKeystore).forEach {
|
||||
it.read {
|
||||
KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray())
|
||||
}
|
||||
}
|
||||
config.trustStoreFile.read {
|
||||
KeyStore.getInstance("JKS").load(it, config.trustStorePassword.toCharArray())
|
||||
|
@ -53,14 +53,14 @@ class ArtemisTcpTransport {
|
||||
)
|
||||
|
||||
if (config != null && enableSSL) {
|
||||
config.keyStoreFile.expectedOnDefaultFileSystem()
|
||||
config.sslKeystore.expectedOnDefaultFileSystem()
|
||||
config.trustStoreFile.expectedOnDefaultFileSystem()
|
||||
val tlsOptions = mapOf<String, Any?>(
|
||||
val tlsOptions = mapOf(
|
||||
// Enable TLS transport layer with client certs and restrict to at least SHA256 in handshake
|
||||
// and AES encryption
|
||||
TransportConstants.SSL_ENABLED_PROP_NAME to true,
|
||||
TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||
TransportConstants.KEYSTORE_PATH_PROP_NAME to config.keyStoreFile,
|
||||
TransportConstants.KEYSTORE_PATH_PROP_NAME to config.sslKeystore,
|
||||
TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to config.keyStorePassword, // TODO proper management of keystores and password
|
||||
TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||
TransportConstants.TRUSTSTORE_PATH_PROP_NAME to config.trustStoreFile,
|
||||
|
@ -7,6 +7,7 @@ interface SSLConfiguration {
|
||||
val keyStorePassword: String
|
||||
val trustStorePassword: String
|
||||
val certificatesDirectory: Path
|
||||
val keyStoreFile: Path get() = certificatesDirectory / "sslkeystore.jks"
|
||||
val sslKeystore: Path get() = certificatesDirectory / "sslkeystore.jks"
|
||||
val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks"
|
||||
val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks"
|
||||
}
|
@ -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
|
@ -40,7 +40,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
|
||||
// javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"]
|
||||
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"]
|
||||
systemProperties['visualvm.display.name'] = 'Corda'
|
||||
systemProperties['jdk.serialFilter'] = 'maxbytes=0'
|
||||
minJavaVersion = '1.8.0'
|
||||
minUpdateVersion['1.8'] = java8_minUpdateVersion
|
||||
caplets = ['CordaCaplet']
|
||||
|
@ -53,7 +53,6 @@ class BootTests {
|
||||
class ObjectInputStreamFlow : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
System.clearProperty("jdk.serialFilter") // This checks that the node has already consumed the property.
|
||||
val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
|
||||
ObjectInputStream(data.inputStream()).use { it.readObject() }
|
||||
}
|
||||
|
@ -5,19 +5,19 @@ import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionType
|
||||
import net.corda.core.crypto.appendToCommonName
|
||||
import net.corda.core.crypto.commonName
|
||||
import net.corda.core.div
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.flows.NotaryError
|
||||
import net.corda.flows.NotaryException
|
||||
import net.corda.flows.NotaryFlow
|
||||
import net.corda.node.internal.AbstractNode
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
|
||||
import net.corda.node.services.transactions.minCorrectReplicas
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.node.utilities.transaction
|
||||
import net.corda.testing.node.NodeBasedTest
|
||||
@ -28,71 +28,55 @@ import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class BFTNotaryServiceTests : NodeBasedTest() {
|
||||
private companion object {
|
||||
val notaryCommonName = X500Name("CN=BFT Notary Server,O=R3,OU=corda,L=Zurich,C=CH")
|
||||
|
||||
fun buildNodeName(it: Int, notaryName: X500Name): X500Name {
|
||||
return notaryName.appendToCommonName("-$it")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detect double spend`() {
|
||||
val masterNode = startBFTNotaryCluster(notaryCommonName, 4, BFTNonValidatingNotaryService.type).first()
|
||||
val clusterName = X500Name("CN=BFT,O=R3,OU=corda,L=Zurich,C=CH")
|
||||
startBFTNotaryCluster(clusterName, 4, BFTNonValidatingNotaryService.type)
|
||||
val alice = startNode(ALICE.name).getOrThrow()
|
||||
|
||||
val notaryParty = alice.netMapCache.getNotary(notaryCommonName)!!
|
||||
|
||||
val notaryParty = alice.netMapCache.getNotary(clusterName)!!
|
||||
val inputState = issueState(alice, notaryParty)
|
||||
|
||||
val firstTxBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState)
|
||||
val firstSpendTx = alice.services.signInitialTransaction(firstTxBuilder)
|
||||
|
||||
val firstSpend = alice.services.startFlow(NotaryFlow.Client(firstSpendTx))
|
||||
firstSpend.resultFuture.getOrThrow()
|
||||
|
||||
val secondSpendBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState).run {
|
||||
val dummyState = DummyContract.SingleOwnerState(0, alice.info.legalIdentity)
|
||||
addOutputState(dummyState)
|
||||
this
|
||||
alice.services.startFlow(NotaryFlow.Client(firstSpendTx)).resultFuture.getOrThrow()
|
||||
val secondSpendBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState).also {
|
||||
it.addOutputState(DummyContract.SingleOwnerState(0, alice.info.legalIdentity))
|
||||
}
|
||||
val secondSpendTx = alice.services.signInitialTransaction(secondSpendBuilder)
|
||||
val secondSpend = alice.services.startFlow(NotaryFlow.Client(secondSpendTx))
|
||||
|
||||
val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() }
|
||||
val ex = assertFailsWith(NotaryException::class) {
|
||||
secondSpend.resultFuture.getOrThrow()
|
||||
}
|
||||
val error = ex.error as NotaryError.Conflict
|
||||
assertEquals(error.txId, secondSpendTx.id)
|
||||
}
|
||||
|
||||
private fun issueState(node: AbstractNode, notary: Party): StateAndRef<*> {
|
||||
return node.database.transaction {
|
||||
val builder = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0))
|
||||
val stx = node.services.signInitialTransaction(builder)
|
||||
node.services.recordTransactions(listOf(stx))
|
||||
private fun issueState(node: AbstractNode, notary: Party) = node.run {
|
||||
database.transaction {
|
||||
val builder = DummyContract.generateInitial(Random().nextInt(), notary, info.legalIdentity.ref(0))
|
||||
val stx = services.signInitialTransaction(builder)
|
||||
services.recordTransactions(listOf(stx))
|
||||
StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0))
|
||||
}
|
||||
}
|
||||
|
||||
private fun startBFTNotaryCluster(notaryName: X500Name,
|
||||
private fun startBFTNotaryCluster(clusterName: X500Name,
|
||||
clusterSize: Int,
|
||||
serviceType: ServiceType): List<Node> {
|
||||
serviceType: ServiceType) {
|
||||
require(clusterSize > 0)
|
||||
val quorum = (2 * clusterSize + 1) / 3
|
||||
val replicaNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
(0 until clusterSize).map { tempFolder.root.toPath() / "${notaryName.commonName}-$it" },
|
||||
replicaNames.map { baseDirectory(it) },
|
||||
serviceType.id,
|
||||
notaryName,
|
||||
quorum)
|
||||
|
||||
val serviceInfo = ServiceInfo(serviceType, notaryName)
|
||||
val nodes = (0 until clusterSize).map {
|
||||
clusterName,
|
||||
minCorrectReplicas(clusterSize))
|
||||
val serviceInfo = ServiceInfo(serviceType, clusterName)
|
||||
val notaryClusterAddresses = (0 until clusterSize).map { "localhost:${11000 + it * 10}" }
|
||||
(0 until clusterSize).forEach {
|
||||
startNode(
|
||||
buildNodeName(it, notaryName),
|
||||
replicaNames[it],
|
||||
advertisedServices = setOf(serviceInfo),
|
||||
configOverrides = mapOf("notaryNodeId" to it)
|
||||
configOverrides = mapOf("bftReplicaId" to it, "notaryClusterAddresses" to notaryClusterAddresses)
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,26 @@
|
||||
package net.corda.services.messaging
|
||||
|
||||
import net.corda.core.copyTo
|
||||
import net.corda.core.createDirectories
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.exists
|
||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.config.SSLConfiguration
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.messaging.SimpleMQClient
|
||||
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
|
||||
/**
|
||||
* Runs the security tests with the attacker pretending to be a node on the network.
|
||||
@ -66,4 +78,56 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
attacker.start(PEER_USER, PEER_USER, enableSSL = false) // Login as a peer
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login with invalid certificate chain`() {
|
||||
val sslConfig = object : SSLConfiguration {
|
||||
override val certificatesDirectory = Files.createTempDirectory("certs")
|
||||
override val keyStorePassword: String get() = "cordacadevpass"
|
||||
override val trustStorePassword: String get() = "trustpass"
|
||||
|
||||
init {
|
||||
val legalName = MEGA_CORP.name
|
||||
certificatesDirectory.createDirectories()
|
||||
if (!trustStoreFile.exists()) {
|
||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
}
|
||||
|
||||
val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
|
||||
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val intermediateCA = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
val clientKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
|
||||
// Set name constrain to the legal name.
|
||||
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf())
|
||||
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCA.certificate, intermediateCA.keyPair, legalName, clientKey.public, nameConstraints = nameConstraints)
|
||||
val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
// Using different x500 name in the TLS cert which is not allowed in the name constraints.
|
||||
val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, MINI_CORP.name, tlsKey.public)
|
||||
val keyPass = keyStorePassword.toCharArray()
|
||||
val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(nodeKeystore, keyStorePassword)
|
||||
clientCAKeystore.addOrReplaceKey(
|
||||
X509Utilities.CORDA_CLIENT_CA,
|
||||
clientKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientCACert, intermediateCA.certificate, rootCACert))
|
||||
clientCAKeystore.save(nodeKeystore, keyStorePassword)
|
||||
|
||||
val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeystore, keyStorePassword)
|
||||
tlsKeystore.addOrReplaceKey(
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))
|
||||
tlsKeystore.save(sslKeystore, keyStorePassword)
|
||||
}
|
||||
}
|
||||
|
||||
val attacker = clientTo(alice.configuration.p2pAddress, sslConfig)
|
||||
|
||||
assertThatExceptionOfType(ActiveMQNotConnectedException::class.java).isThrownBy {
|
||||
attacker.start(PEER_USER, PEER_USER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ package net.corda.services.messaging
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.commonName
|
||||
import net.corda.core.messaging.MessageRecipients
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
||||
@ -64,10 +62,8 @@ class P2PMessagingTest : NodeBasedTest() {
|
||||
// TODO Use a dummy distributed service
|
||||
@Test
|
||||
fun `communicating with a distributed service which the network map node is part of`() {
|
||||
|
||||
val root = tempFolder.root.toPath()
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
listOf(root / DUMMY_MAP.name.commonName, root / SERVICE_2_NAME.commonName),
|
||||
listOf(DUMMY_MAP.name, SERVICE_2_NAME).map { baseDirectory(it) },
|
||||
RaftValidatingNotaryService.type.id,
|
||||
DISTRIBUTED_SERVICE_NAME)
|
||||
|
||||
|
@ -2,8 +2,6 @@ package net.corda.services.messaging
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.commonName
|
||||
import net.corda.core.div
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
@ -60,7 +58,7 @@ class P2PSecurityTest : NodeBasedTest() {
|
||||
|
||||
private fun startSimpleNode(legalName: X500Name): SimpleNode {
|
||||
val config = TestNodeConfiguration(
|
||||
baseDirectory = tempFolder.root.toPath() / legalName.commonName,
|
||||
baseDirectory = baseDirectory(legalName),
|
||||
myLegalName = legalName,
|
||||
networkMapService = NetworkMapInfo(networkMapNode.configuration.p2pAddress, networkMapNode.info.legalIdentity.name))
|
||||
config.configureWithDevSSLCertificate() // This creates the node's TLS cert with the CN as the legal name
|
||||
|
@ -10,10 +10,10 @@ import net.corda.core.crypto.commonName
|
||||
import net.corda.core.crypto.orgName
|
||||
import net.corda.core.node.VersionInfo
|
||||
import net.corda.core.utilities.Emoji
|
||||
import net.corda.core.utilities.LogHelper.withLevel
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.enforceSingleNodeIsRunning
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
import net.corda.node.services.transactions.bftSMaRtSerialFilter
|
||||
import net.corda.node.shell.InteractiveShell
|
||||
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
||||
import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
||||
@ -21,7 +21,6 @@ import org.fusesource.jansi.Ansi
|
||||
import org.fusesource.jansi.AnsiConsole
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler
|
||||
import java.io.*
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.net.InetAddress
|
||||
import java.nio.file.Paths
|
||||
@ -72,8 +71,6 @@ fun main(args: Array<String>) {
|
||||
enforceSingleNodeIsRunning(cmdlineOptions.baseDirectory)
|
||||
|
||||
initLogging(cmdlineOptions)
|
||||
disableJavaDeserialization() // Should be after initLogging to avoid TMI.
|
||||
|
||||
// Manifest properties are only available if running from the corda jar
|
||||
fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
|
||||
|
||||
@ -107,7 +104,7 @@ fun main(args: Array<String>) {
|
||||
println("Unable to load the configuration file: ${e.rootCause.message}")
|
||||
exitProcess(2)
|
||||
}
|
||||
|
||||
SerialFilter.install(if (conf.bftReplicaId != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
|
||||
if (cmdlineOptions.isRegistration) {
|
||||
println()
|
||||
println("******************************************************************")
|
||||
@ -208,29 +205,12 @@ private fun assertCanNormalizeEmptyPath() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun failStartUp(message: String): Nothing {
|
||||
internal fun failStartUp(message: String): Nothing {
|
||||
println(message)
|
||||
println("Corda will now exit...")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
private fun disableJavaDeserialization() {
|
||||
// ObjectInputFilter and friends are in java.io in Java 9 but sun.misc in backports, so we are using the system property interface for portability.
|
||||
// This property has already been set in the Capsule. Anywhere else may be too late, but we'll repeat it here for developers.
|
||||
System.setProperty("jdk.serialFilter", "maxbytes=0")
|
||||
// Attempt at deserialization so that ObjectInputFilter (permanently) inits itself:
|
||||
val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
|
||||
try {
|
||||
withLevel("java.io.serialization", "WARN") {
|
||||
ObjectInputStream(data.inputStream()).use { it.readObject() } // Logs REJECTED at INFO, which we don't want users to see.
|
||||
}
|
||||
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
|
||||
failStartUp("Corda forbids Java deserialisation. Please upgrade to at least JDK 8u121 and set system property 'jdk.serialFilter' to 'maxbytes=0' when booting Corda.")
|
||||
} catch (e: InvalidClassException) {
|
||||
// Good, our system property is honoured.
|
||||
}
|
||||
}
|
||||
|
||||
private fun printPluginsAndServices(node: Node) {
|
||||
node.configuration.extraAdvertisedServiceIds.let {
|
||||
if (it.isNotEmpty()) printBasicNodeInfo("Providing network services", it.joinToString())
|
||||
|
62
node/src/main/kotlin/net/corda/node/SerialFilter.kt
Normal file
62
node/src/main/kotlin/net/corda/node/SerialFilter.kt
Normal 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
|
@ -32,6 +32,8 @@ import net.corda.nodeapi.config.SSLConfiguration
|
||||
import net.corda.nodeapi.config.parseAs
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.cordform.CordformContext
|
||||
import net.corda.core.internal.ShutdownHook
|
||||
import net.corda.core.internal.addShutdownHook
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
@ -236,22 +238,19 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
|
||||
coerce: (D) -> DI,
|
||||
dsl: DI.() -> A
|
||||
): A {
|
||||
var shutdownHook: Thread? = null
|
||||
var shutdownHook: ShutdownHook? = null
|
||||
try {
|
||||
driverDsl.start()
|
||||
shutdownHook = Thread({
|
||||
shutdownHook = addShutdownHook {
|
||||
driverDsl.shutdown()
|
||||
})
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook)
|
||||
}
|
||||
return dsl(coerce(driverDsl))
|
||||
} catch (exception: Throwable) {
|
||||
log.error("Driver shutting down because of exception", exception)
|
||||
throw exception
|
||||
} finally {
|
||||
driverDsl.shutdown()
|
||||
if (shutdownHook != null) {
|
||||
Runtime.getRuntime().removeShutdownHook(shutdownHook)
|
||||
}
|
||||
shutdownHook?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@ -558,21 +557,19 @@ class DriverDSL(
|
||||
verifierType: VerifierType,
|
||||
rpcUsers: List<User>
|
||||
): ListenableFuture<Pair<Party, List<NodeHandle>>> {
|
||||
val nodeNames = (1..clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(it.toString()) }
|
||||
val nodeNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
|
||||
val paths = nodeNames.map { baseDirectory(it) }
|
||||
ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName)
|
||||
|
||||
val serviceInfo = ServiceInfo(type, notaryName)
|
||||
val advertisedService = setOf(serviceInfo)
|
||||
val advertisedServices = setOf(ServiceInfo(type, notaryName))
|
||||
val notaryClusterAddress = portAllocation.nextHostAndPort()
|
||||
|
||||
// Start the first node that will bootstrap the cluster
|
||||
val firstNotaryFuture = startNode(nodeNames.first(), advertisedService, rpcUsers, verifierType, mapOf("notaryNodeAddress" to notaryClusterAddress.toString()))
|
||||
val firstNotaryFuture = startNode(nodeNames.first(), advertisedServices, rpcUsers, verifierType, mapOf("notaryNodeAddress" to notaryClusterAddress.toString()))
|
||||
// All other nodes will join the cluster
|
||||
val restNotaryFutures = nodeNames.drop(1).map {
|
||||
val nodeAddress = portAllocation.nextHostAndPort()
|
||||
val configOverride = mapOf("notaryNodeAddress" to nodeAddress.toString(), "notaryClusterAddresses" to listOf(notaryClusterAddress.toString()))
|
||||
startNode(it, advertisedService, rpcUsers, verifierType, configOverride)
|
||||
startNode(it, advertisedServices, rpcUsers, verifierType, configOverride)
|
||||
}
|
||||
|
||||
return firstNotaryFuture.flatMap { firstNotary ->
|
||||
|
@ -61,6 +61,7 @@ import org.jetbrains.exposed.sql.Database
|
||||
import org.slf4j.Logger
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.Modifier.*
|
||||
import java.net.InetAddress
|
||||
import java.net.URL
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Path
|
||||
@ -390,7 +391,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
protected open fun makeServiceEntries(): List<ServiceEntry> {
|
||||
return advertisedServices.map {
|
||||
val serviceId = it.type.id
|
||||
val serviceName = it.name ?: configuration.myLegalName.replaceCommonName(serviceId)
|
||||
val serviceName = it.name ?: X500Name("${configuration.myLegalName},OU=$serviceId")
|
||||
val identity = obtainKeyPair(serviceId, serviceName).first
|
||||
ServiceEntry(it, identity)
|
||||
}
|
||||
@ -400,16 +401,16 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
protected open fun acceptableLiveFiberCountOnStop(): Int = 0
|
||||
|
||||
private fun hasSSLCertificates(): Boolean {
|
||||
val keyStore = try {
|
||||
val (sslKeystore, keystore) = try {
|
||||
// This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
|
||||
KeyStoreUtilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
Pair(KeyStoreUtilities.loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword), KeyStoreUtilities.loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword))
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
return false
|
||||
} catch (e: KeyStoreException) {
|
||||
log.warn("Certificate key store found but key store password does not match configuration.")
|
||||
null
|
||||
return false
|
||||
}
|
||||
return keyStore?.containsAlias(X509Utilities.CORDA_CLIENT_CA) ?: false
|
||||
return sslKeystore.containsAlias(X509Utilities.CORDA_CLIENT_TLS) && keystore.containsAlias(X509Utilities.CORDA_CLIENT_CA)
|
||||
}
|
||||
|
||||
// Specific class so that MockNode can catch it.
|
||||
@ -518,10 +519,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
RaftNonValidatingNotaryService.type -> RaftNonValidatingNotaryService(timestampChecker, uniquenessProvider as RaftUniquenessProvider)
|
||||
RaftValidatingNotaryService.type -> RaftValidatingNotaryService(timestampChecker, uniquenessProvider as RaftUniquenessProvider)
|
||||
BFTNonValidatingNotaryService.type -> with(configuration as FullNodeConfiguration) {
|
||||
val nodeId = notaryNodeId ?: throw IllegalArgumentException("notaryNodeId value must be specified in the configuration")
|
||||
val client = BFTSMaRt.Client(nodeId)
|
||||
tokenizableServices += client
|
||||
BFTNonValidatingNotaryService(services, timestampChecker, nodeId, database, client)
|
||||
val replicaId = bftReplicaId ?: throw IllegalArgumentException("bftReplicaId value must be specified in the configuration")
|
||||
BFTSMaRtConfig(notaryClusterAddresses).use { config ->
|
||||
val client = BFTSMaRt.Client(config, replicaId).also { tokenizableServices += it } // (Ab)use replicaId for clientId.
|
||||
BFTNonValidatingNotaryService(config, services, timestampChecker, replicaId, database, client)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("Notary type ${type.id} is not handled by makeNotaryService.")
|
||||
@ -598,12 +600,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
// the legal name is actually validated in some way.
|
||||
|
||||
// TODO: Integrate with Key management service?
|
||||
val keystore = KeyStoreUtilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
val keystore = KeyStoreUtilities.loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
val clientCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, configuration.keyStorePassword)
|
||||
val privateKeyAlias = "$serviceId-private-key"
|
||||
val privKeyFile = configuration.baseDirectory / privateKeyAlias
|
||||
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
|
||||
|
||||
val identityAndKey = if (configuration.keyStoreFile.exists() && keystore.containsAlias(privateKeyAlias)) {
|
||||
val identityAndKey = if (configuration.nodeKeystore.exists() && keystore.containsAlias(privateKeyAlias)) {
|
||||
// Get keys from keystore.
|
||||
val (cert, keyPair) = keystore.getCertificateAndKeyPair(privateKeyAlias, configuration.keyStorePassword)
|
||||
val loadedServiceName = X509CertificateHolder(cert.encoded).subject
|
||||
@ -624,19 +627,18 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
"$serviceName vs ${myIdentity.name}")
|
||||
// Load the private key.
|
||||
val keyPair = privKeyFile.readAll().deserialize<KeyPair>()
|
||||
// TODO: Use a proper certificate chain.
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(serviceName, keyPair)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keystore.save(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(cert, *keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
|
||||
keystore.save(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
Pair(myIdentity, keyPair)
|
||||
} else {
|
||||
// Create new keys and store in keystore.
|
||||
log.info("Identity key not found, generating fresh key!")
|
||||
val keyPair: KeyPair = generateKeyPair()
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(serviceName, keyPair)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, selfSignCert.keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keystore.save(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
Pair(Party(serviceName, selfSignCert.keyPair.public), selfSignCert.keyPair)
|
||||
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(cert, *keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
|
||||
keystore.save(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
Pair(Party(serviceName, keyPair.public), keyPair)
|
||||
}
|
||||
partyKeys += identityAndKey.second
|
||||
return identityAndKey
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import net.corda.core.internal.addShutdownHook
|
||||
import net.corda.core.div
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import java.io.RandomAccessFile
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.nio.file.Path
|
||||
@ -26,9 +26,9 @@ fun enforceSingleNodeIsRunning(baseDirectory: Path) {
|
||||
}
|
||||
// Avoid the lock being garbage collected. We don't really need to release it as the OS will do so for us
|
||||
// when our process shuts down, but we try in stop() anyway just to be nice.
|
||||
Runtime.getRuntime().addShutdownHook(Thread {
|
||||
addShutdownHook {
|
||||
pidFileLock.release()
|
||||
})
|
||||
}
|
||||
val ourProcessID: String = ManagementFactory.getRuntimeMXBean().name.split("@")[0]
|
||||
pidFileRw.setLength(0)
|
||||
pidFileRw.write(ourProcessID.toByteArray())
|
||||
|
@ -5,16 +5,15 @@ import com.google.common.net.HostAndPort
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import net.corda.core.flatMap
|
||||
import net.corda.core.*
|
||||
import net.corda.core.internal.ShutdownHook
|
||||
import net.corda.core.internal.addShutdownHook
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.minutes
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.VersionInfo
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.success
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.node.printBasicNodeInfo
|
||||
@ -47,7 +46,6 @@ import java.io.IOException
|
||||
import java.time.Clock
|
||||
import java.util.*
|
||||
import javax.management.ObjectName
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* A Node manages a standalone server that takes part in the P2P network. It creates the services found in [ServiceHub],
|
||||
@ -112,7 +110,7 @@ class Node(override val configuration: FullNodeConfiguration,
|
||||
|
||||
var messageBroker: ArtemisMessagingServer? = null
|
||||
|
||||
private var shutdownThread: Thread? = null
|
||||
private var shutdownHook: ShutdownHook? = null
|
||||
|
||||
private lateinit var userService: RPCUserService
|
||||
|
||||
@ -295,12 +293,9 @@ class Node(override val configuration: FullNodeConfiguration,
|
||||
|
||||
(startupComplete as SettableFuture<Unit>).set(Unit)
|
||||
}
|
||||
|
||||
shutdownThread = thread(start = false) {
|
||||
shutdownHook = addShutdownHook {
|
||||
stop()
|
||||
}
|
||||
Runtime.getRuntime().addShutdownHook(shutdownThread)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
@ -322,12 +317,9 @@ class Node(override val configuration: FullNodeConfiguration,
|
||||
synchronized(this) {
|
||||
if (shutdown) return
|
||||
shutdown = true
|
||||
|
||||
// Unregister shutdown hook to prevent any unnecessary second calls to stop
|
||||
if ((shutdownThread != null) && (Thread.currentThread() != shutdownThread)) {
|
||||
Runtime.getRuntime().removeShutdownHook(shutdownThread)
|
||||
shutdownThread = null
|
||||
}
|
||||
shutdownHook?.cancel()
|
||||
shutdownHook = null
|
||||
}
|
||||
printBasicNodeInfo("Shutting down ...")
|
||||
|
||||
|
@ -54,8 +54,8 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: X500Name) {
|
||||
if (!trustStoreFile.exists()) {
|
||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
}
|
||||
if (!keyStoreFile.exists()) {
|
||||
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
||||
val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
X509Utilities.createKeystoreForSSL(keyStoreFile, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
|
||||
X509Utilities.createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ data class FullNodeConfiguration(
|
||||
// Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one
|
||||
val messagingServerAddress: HostAndPort?,
|
||||
val extraAdvertisedServiceIds: List<String>,
|
||||
val notaryNodeId: Int?,
|
||||
val bftReplicaId: Int?,
|
||||
val notaryNodeAddress: HostAndPort?,
|
||||
val notaryClusterAddresses: List<HostAndPort>,
|
||||
override val certificateChainCheckPolicies: List<CertChainPolicyConfig>,
|
||||
|
@ -6,7 +6,7 @@ import com.google.common.util.concurrent.SettableFuture
|
||||
import io.netty.handler.ssl.SslHandler
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
@ -30,10 +30,7 @@ import org.apache.activemq.artemis.core.config.Configuration
|
||||
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
|
||||
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
|
||||
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.*
|
||||
import org.apache.activemq.artemis.core.security.Role
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
||||
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
||||
@ -46,8 +43,11 @@ import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateCallback
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
|
||||
import org.apache.activemq.artemis.utils.ConfigurationHelper
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import rx.Subscription
|
||||
import sun.security.x509.X509CertImpl
|
||||
import java.io.IOException
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyStore
|
||||
@ -259,9 +259,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
|
||||
@Throws(IOException::class, KeyStoreException::class)
|
||||
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword)
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_CA)
|
||||
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_TLS)
|
||||
|
||||
val ourSubjectDN = X500Name(ourCertificate.subjectDN.name)
|
||||
// This is a sanity check and should not fail unless things have been misconfigured
|
||||
@ -423,9 +423,8 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
private fun getBridgeName(queueName: String, hostAndPort: HostAndPort): String = "$queueName -> $hostAndPort"
|
||||
|
||||
// This is called on one of Artemis' background threads
|
||||
internal fun hostVerificationFail(peerLegalName: X500Name, expectedLegalName: X500Name) {
|
||||
log.error("Peer has wrong CN - expected $expectedLegalName but got $peerLegalName. This is either a fatal " +
|
||||
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!")
|
||||
internal fun hostVerificationFail(expectedLegalName: X500Name, errorMsg: String?) {
|
||||
log.error(errorMsg)
|
||||
if (expectedLegalName == config.networkMapService?.legalName) {
|
||||
// If the peer that failed host verification was the network map node then we're in big trouble and need to bail!
|
||||
_networkMapConnectionFuture!!.setException(IOException("${config.networkMapService} failed host verification check"))
|
||||
@ -466,7 +465,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
}
|
||||
|
||||
class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
|
||||
override fun createConnector(configuration: MutableMap<String, Any>?,
|
||||
override fun createConnector(configuration: MutableMap<String, Any>,
|
||||
handler: BufferHandler?,
|
||||
listener: ClientConnectionLifeCycleListener?,
|
||||
closeExecutor: Executor?,
|
||||
@ -478,7 +477,7 @@ class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
|
||||
}
|
||||
}
|
||||
|
||||
private class VerifyingNettyConnector(configuration: MutableMap<String, Any>?,
|
||||
private class VerifyingNettyConnector(configuration: MutableMap<String, Any>,
|
||||
handler: BufferHandler?,
|
||||
listener: ClientConnectionLifeCycleListener?,
|
||||
closeExecutor: Executor?,
|
||||
@ -486,27 +485,37 @@ private class VerifyingNettyConnector(configuration: MutableMap<String, Any>?,
|
||||
scheduledThreadPool: ScheduledExecutorService?,
|
||||
protocolManager: ClientProtocolManager?) :
|
||||
NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) {
|
||||
private val server = configuration?.get(ArtemisMessagingServer::class.java.name) as? ArtemisMessagingServer
|
||||
private val expecteLegalName: X500Name? = configuration?.get(ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME) as X500Name?
|
||||
private val server = configuration[ArtemisMessagingServer::class.java.name] as ArtemisMessagingServer
|
||||
private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration)
|
||||
|
||||
override fun createConnection(): Connection? {
|
||||
val connection = super.createConnection() as NettyConnection?
|
||||
if (connection != null && expecteLegalName != null) {
|
||||
val peerLegalName: X500Name = connection
|
||||
.channel
|
||||
.pipeline()
|
||||
.get(SslHandler::class.java)
|
||||
.engine()
|
||||
.session
|
||||
.peerPrincipal
|
||||
.name
|
||||
.let(::X500Name)
|
||||
if (peerLegalName != expecteLegalName) {
|
||||
val connection = super.createConnection() as? NettyConnection
|
||||
if (sslEnabled && connection != null) {
|
||||
val expectedLegalName = configuration[ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME] as X500Name
|
||||
try {
|
||||
val session = connection.channel
|
||||
.pipeline()
|
||||
.get(SslHandler::class.java)
|
||||
.engine()
|
||||
.session
|
||||
// Checks the peer name is the one we are expecting.
|
||||
val peerLegalName = session.peerPrincipal.name.let(::X500Name)
|
||||
require(peerLegalName == expectedLegalName) {
|
||||
"Peer has wrong CN - expected $expectedLegalName but got $peerLegalName. This is either a fatal " +
|
||||
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
|
||||
}
|
||||
// Make sure certificate has the same name.
|
||||
val peerCertificate = X509CertificateHolder(session.peerCertificateChain.first().encoded)
|
||||
require(peerCertificate.subject == expectedLegalName) {
|
||||
"Peer has wrong subject name in the certificate - expected $expectedLegalName but got ${peerCertificate.subject}. This is either a fatal " +
|
||||
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
|
||||
}
|
||||
X509Utilities.validateCertificateChain(X509CertImpl(session.localCertificates.last().encoded), *session.peerCertificates)
|
||||
server.onTcpConnection(peerLegalName)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
connection.close()
|
||||
server!!.hostVerificationFail(peerLegalName, expecteLegalName)
|
||||
return null // Artemis will keep trying to reconnect until it's told otherwise
|
||||
} else {
|
||||
server!!.onTcpConnection(peerLegalName)
|
||||
server.hostVerificationFail(expectedLegalName, e.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
return connection
|
||||
@ -547,7 +556,7 @@ sealed class CertificateChainCheckPolicy {
|
||||
|
||||
object LeafMustMatch : CertificateChainCheckPolicy() {
|
||||
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
|
||||
val ourPublicKey = keyStore.getCertificate(CORDA_CLIENT_CA).publicKey
|
||||
val ourPublicKey = keyStore.getCertificate(CORDA_CLIENT_TLS).publicKey
|
||||
return object : Check {
|
||||
override fun checkCertificateChain(theirChain: Array<X509Certificate>) {
|
||||
val theirLeaf = theirChain.first().publicKey
|
||||
|
@ -14,6 +14,7 @@ import net.corda.core.utilities.unwrap
|
||||
import net.corda.flows.NotaryException
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import java.nio.file.Path
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
@ -21,14 +22,18 @@ import kotlin.concurrent.thread
|
||||
*
|
||||
* A transaction is notarised when the consensus is reached by the cluster on its uniqueness, and timestamp validity.
|
||||
*/
|
||||
class BFTNonValidatingNotaryService(services: ServiceHubInternal,
|
||||
class BFTNonValidatingNotaryService(config: BFTSMaRtConfig,
|
||||
services: ServiceHubInternal,
|
||||
timestampChecker: TimestampChecker,
|
||||
serverId: Int,
|
||||
db: Database,
|
||||
val client: BFTSMaRt.Client) : NotaryService {
|
||||
private val client: BFTSMaRt.Client) : NotaryService {
|
||||
init {
|
||||
val configHandle = config.handle()
|
||||
thread(name = "BFTSmartServer-$serverId", isDaemon = true) {
|
||||
Server(serverId, db, "bft_smart_notary_committed_states", services, timestampChecker)
|
||||
configHandle.use {
|
||||
Server(configHandle.path, serverId, db, "bft_smart_notary_committed_states", services, timestampChecker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,11 +67,12 @@ class BFTNonValidatingNotaryService(services: ServiceHubInternal,
|
||||
}
|
||||
}
|
||||
|
||||
private class Server(id: Int,
|
||||
private class Server(configHome: Path,
|
||||
id: Int,
|
||||
db: Database,
|
||||
tableName: String,
|
||||
services: ServiceHubInternal,
|
||||
timestampChecker: TimestampChecker) : BFTSMaRt.Server(id, db, tableName, services, timestampChecker) {
|
||||
timestampChecker: TimestampChecker) : BFTSMaRt.Server(configHome, id, db, tableName, services, timestampChecker) {
|
||||
|
||||
override fun executeCommand(command: ByteArray): ByteArray {
|
||||
val request = command.deserialize<BFTSMaRt.CommitRequest>()
|
||||
|
@ -32,6 +32,7 @@ import net.corda.node.services.transactions.BFTSMaRt.Server
|
||||
import net.corda.node.utilities.JDBCHashMap
|
||||
import net.corda.node.utilities.transaction
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -66,13 +67,17 @@ object BFTSMaRt {
|
||||
data class Signatures(val txSignatures: List<DigitalSignature>) : ClusterResponse()
|
||||
}
|
||||
|
||||
class Client(val id: Int) : SingletonSerializeAsToken() {
|
||||
class Client(config: BFTSMaRtConfig, private val clientId: Int) : SingletonSerializeAsToken() {
|
||||
private val configHandle = config.handle()
|
||||
|
||||
companion object {
|
||||
private val log = loggerFor<Client>()
|
||||
}
|
||||
|
||||
/** A proxy for communicating with the BFT cluster */
|
||||
private val proxy: ServiceProxy by lazy { buildProxy() }
|
||||
private val proxy: ServiceProxy by lazy {
|
||||
configHandle.use { buildProxy(it.path) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a transaction commit request to the BFT cluster. The [proxy] will deliver the request to every
|
||||
@ -86,10 +91,10 @@ object BFTSMaRt {
|
||||
return response
|
||||
}
|
||||
|
||||
private fun buildProxy(): ServiceProxy {
|
||||
private fun buildProxy(configHome: Path): ServiceProxy {
|
||||
val comparator = buildResponseComparator()
|
||||
val extractor = buildExtractor()
|
||||
return ServiceProxy(id, "bft-smart-config", comparator, extractor)
|
||||
return ServiceProxy(clientId, configHome.toString(), comparator, extractor)
|
||||
}
|
||||
|
||||
/** A comparator to check if replies from two replicas are the same. */
|
||||
@ -111,7 +116,7 @@ object BFTSMaRt {
|
||||
val accepted = responses.filterIsInstance<ReplicaResponse.Signature>()
|
||||
val rejected = responses.filterIsInstance<ReplicaResponse.Error>()
|
||||
|
||||
log.debug { "BFT Client $id: number of replicas accepted the commit: ${accepted.size}, rejected: ${rejected.size}" }
|
||||
log.debug { "BFT Client $clientId: number of replicas accepted the commit: ${accepted.size}, rejected: ${rejected.size}" }
|
||||
|
||||
// TODO: only return an aggregate if the majority of signatures are replies
|
||||
// TODO: return an error reported by the majority and not just the first one
|
||||
@ -137,7 +142,8 @@ object BFTSMaRt {
|
||||
* The validation logic can be specified by implementing the [executeCommand] method.
|
||||
*/
|
||||
@Suppress("LeakingThis")
|
||||
abstract class Server(val id: Int,
|
||||
abstract class Server(configHome: Path,
|
||||
val replicaId: Int,
|
||||
val db: Database,
|
||||
tableName: String,
|
||||
val services: ServiceHubInternal,
|
||||
@ -152,7 +158,7 @@ object BFTSMaRt {
|
||||
|
||||
init {
|
||||
// TODO: Looks like this statement is blocking. Investigate the bft-smart node startup.
|
||||
ServiceReplica(id, "bft-smart-config", this, this, null, DefaultReplier())
|
||||
ServiceReplica(replicaId, configHome.toString(), this, this, null, DefaultReplier())
|
||||
}
|
||||
|
||||
override fun appExecuteUnordered(command: ByteArray, msgCtx: MessageContext): ByteArray? {
|
||||
|
@ -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.")
|
||||
}
|
@ -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())
|
@ -127,7 +127,7 @@ class RaftUniquenessProvider(
|
||||
return NettyTransport.builder()
|
||||
.withSsl()
|
||||
.withSslProtocol(SslProtocol.TLSv1_2)
|
||||
.withKeyStorePath(config.keyStoreFile.toString())
|
||||
.withKeyStorePath(config.sslKeystore.toString())
|
||||
.withKeyStorePassword(config.keyStorePassword)
|
||||
.withTrustStorePath(config.trustStoreFile.toString())
|
||||
.withTrustStorePassword(config.trustStorePassword)
|
||||
|
@ -3,6 +3,7 @@ package net.corda.node.utilities.registration
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
@ -31,15 +32,16 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
|
||||
fun buildKeystore() {
|
||||
config.certificatesDirectory.createDirectories()
|
||||
val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword)
|
||||
val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.nodeKeystore, keystorePassword)
|
||||
if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) {
|
||||
// Create or load self signed keypair from the key store.
|
||||
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
|
||||
if (!caKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) {
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(config.myLegalName)
|
||||
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair)
|
||||
// Save to the key store.
|
||||
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, selfSignCert.keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
caKeyStore.save(config.keyStoreFile, keystorePassword)
|
||||
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert))
|
||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
}
|
||||
val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
|
||||
val requestId = submitOrResumeCertificateSigningRequest(keyPair)
|
||||
@ -58,13 +60,22 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
// Save private key and certificate chain to the key store.
|
||||
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
|
||||
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
|
||||
caKeyStore.save(config.keyStoreFile, keystorePassword)
|
||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
// Save root certificates to trust store.
|
||||
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
// Assumes certificate chain always starts with client certificate and end with root certificate.
|
||||
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last())
|
||||
trustStore.save(config.trustStoreFile, config.trustStorePassword)
|
||||
println("Certificate and private key stored in ${config.keyStoreFile}.")
|
||||
println("Node private key and certificate stored in ${config.nodeKeystore}.")
|
||||
|
||||
println("Generating SSL certificate for node messaging service.")
|
||||
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA)
|
||||
val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, caCert.subject, sslKey.public)
|
||||
val sslKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.sslKeystore, keystorePassword)
|
||||
sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates))
|
||||
sslKeyStore.save(config.sslKeystore, config.keyStorePassword)
|
||||
println("SSL private key and certificate stored in ${config.sslKeystore}.")
|
||||
// All done, clean up temp files.
|
||||
requestIdStore.deleteIfExists()
|
||||
} else {
|
||||
@ -72,6 +83,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Poll Certificate Signing Server for approved certificate,
|
||||
* enter a slow polling loop if server return null.
|
||||
|
Binary file not shown.
Binary file not shown.
@ -32,10 +32,10 @@ system.communication.defaultkeys = true
|
||||
############################################
|
||||
|
||||
#Number of servers in the group
|
||||
system.servers.num = 4
|
||||
system.servers.num = %s
|
||||
|
||||
#Maximum number of faulty replicas
|
||||
system.servers.f = 1
|
||||
system.servers.f = %s
|
||||
|
||||
#Timeout to asking for a client request
|
||||
system.totalordermulticast.timeout = 2000
|
31
node/src/test/kotlin/net/corda/node/SerialFilterTests.kt
Normal file
31
node/src/test/kotlin/net/corda/node/SerialFilterTests.kt
Normal 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)
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import net.corda.core.crypto.commonName
|
||||
import net.corda.core.div
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.core.utilities.WHITESPACE
|
||||
import net.corda.testing.node.NodeBasedTest
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
@ -12,11 +13,8 @@ import org.junit.Test
|
||||
class NodeTest : NodeBasedTest() {
|
||||
@Test
|
||||
fun `empty plugins directory`() {
|
||||
val baseDirectory = tempFolder.root.toPath() / ALICE.name.commonName
|
||||
val baseDirectory = baseDirectory(ALICE.name)
|
||||
(baseDirectory / "plugins").createDirectories()
|
||||
val node = startNode(ALICE.name).getOrThrow()
|
||||
// Make sure we created the plugins dir in the correct place
|
||||
assertThat(baseDirectory).isEqualTo(node.configuration.baseDirectory)
|
||||
|
||||
startNode(ALICE.name).getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
@ -66,13 +65,13 @@ class InMemoryIdentityServiceTests {
|
||||
*/
|
||||
@Test
|
||||
fun `assert unknown anonymous key is unrecognised`() {
|
||||
val rootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name)
|
||||
val txCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, rootCertAndKey)
|
||||
val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey)
|
||||
val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val service = InMemoryIdentityService()
|
||||
val rootKey = rootCertAndKey.keyPair
|
||||
// TODO: Generate certificate with an EdDSA key rather than ECDSA
|
||||
val identity = Party(rootCertAndKey)
|
||||
val txIdentity = AnonymousParty(txCertAndKey.keyPair.public)
|
||||
val identity = Party(CertificateAndKeyPair(rootCert, rootKey))
|
||||
val txIdentity = AnonymousParty(txKey.public)
|
||||
|
||||
assertFailsWith<IdentityService.UnknownAnonymousPartyException> {
|
||||
service.assertOwnership(identity, txIdentity)
|
||||
@ -85,20 +84,26 @@ class InMemoryIdentityServiceTests {
|
||||
*/
|
||||
@Test
|
||||
fun `assert ownership`() {
|
||||
val aliceRootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name)
|
||||
val aliceTxCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, aliceRootCertAndKey)
|
||||
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCertAndKey, aliceTxCertAndKey.certificate, false).certPath
|
||||
val bobRootCertAndKey = X509Utilities.createSelfSignedCACert(BOB.name)
|
||||
val bobTxCertAndKey = X509Utilities.createIntermediateCert(BOB.name, bobRootCertAndKey)
|
||||
val bobCertPath = X509Utilities.createCertificatePath(bobRootCertAndKey, bobTxCertAndKey.certificate, false).certPath
|
||||
val service = InMemoryIdentityService()
|
||||
val alice = Party(aliceRootCertAndKey)
|
||||
val anonymousAlice = AnonymousParty(aliceTxCertAndKey.keyPair.public)
|
||||
val bob = Party(bobRootCertAndKey)
|
||||
val anonymousBob = AnonymousParty(bobTxCertAndKey.keyPair.public)
|
||||
val aliceRootKey = Crypto.generateKeyPair()
|
||||
val aliceRootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, aliceRootKey)
|
||||
val aliceTxKey = Crypto.generateKeyPair()
|
||||
val aliceTxCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, aliceRootCert, aliceRootKey, ALICE.name, aliceTxKey.public)
|
||||
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCert, aliceTxCert, revocationEnabled = false)
|
||||
|
||||
service.registerPath(aliceRootCertAndKey.certificate, anonymousAlice, aliceCertPath)
|
||||
service.registerPath(bobRootCertAndKey.certificate, anonymousBob, bobCertPath)
|
||||
val bobRootKey = Crypto.generateKeyPair()
|
||||
val bobRootCert = X509Utilities.createSelfSignedCACertificate(BOB.name, bobRootKey)
|
||||
val bobTxKey = Crypto.generateKeyPair()
|
||||
val bobTxCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, bobRootCert, bobRootKey, BOB.name, bobTxKey.public)
|
||||
val bobCertPath = X509Utilities.createCertificatePath(bobRootCert, bobTxCert, revocationEnabled = false)
|
||||
|
||||
val service = InMemoryIdentityService()
|
||||
val alice = Party(CertificateAndKeyPair(aliceRootCert, aliceRootKey))
|
||||
val anonymousAlice = AnonymousParty(aliceTxKey.public)
|
||||
val bob = Party(CertificateAndKeyPair(bobRootCert, bobRootKey))
|
||||
val anonymousBob = AnonymousParty(bobTxKey.public)
|
||||
|
||||
service.registerPath(aliceRootCert, anonymousAlice, aliceCertPath)
|
||||
service.registerPath(bobRootCert, anonymousBob, bobCertPath)
|
||||
|
||||
// Verify that paths are verified
|
||||
service.assertOwnership(alice, anonymousAlice)
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
@ -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 }
|
||||
}
|
||||
}
|
@ -3,14 +3,12 @@ package net.corda.node.utilities.registration
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.eq
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import net.corda.core.crypto.KeyStoreUtilities
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.exists
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.testing.TestNodeConfiguration
|
||||
import net.corda.testing.getTestX509Name
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -31,7 +29,7 @@ class NetworkRegistrationHelperTest {
|
||||
"CORDA_INTERMEDIATE_CA",
|
||||
"CORDA_ROOT_CA")
|
||||
.map { getTestX509Name(it) }
|
||||
val certs = identities.map { X509Utilities.createSelfSignedCACert(it).certificate }
|
||||
val certs = identities.map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
|
||||
.toTypedArray()
|
||||
|
||||
val certService: NetworkRegistrationService = mock {
|
||||
@ -44,32 +42,45 @@ class NetworkRegistrationHelperTest {
|
||||
myLegalName = ALICE.name,
|
||||
networkMapService = null)
|
||||
|
||||
assertFalse(config.keyStoreFile.exists())
|
||||
assertFalse(config.nodeKeystore.exists())
|
||||
assertFalse(config.sslKeystore.exists())
|
||||
assertFalse(config.trustStoreFile.exists())
|
||||
|
||||
NetworkRegistrationHelper(config, certService).buildKeystore()
|
||||
|
||||
assertTrue(config.keyStoreFile.exists())
|
||||
assertTrue(config.nodeKeystore.exists())
|
||||
assertTrue(config.sslKeystore.exists())
|
||||
assertTrue(config.trustStoreFile.exists())
|
||||
|
||||
KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword).run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
|
||||
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||
assertEquals(3, certificateChain.size)
|
||||
val nodeKeystore = KeyStoreUtilities.loadKeyStore(config.nodeKeystore, config.keyStorePassword)
|
||||
val sslKeystore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
|
||||
|
||||
nodeKeystore.run {
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
|
||||
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||
assertEquals(3, certificateChain.size)
|
||||
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { X509CertificateHolder(it.encoded).subject.commonName })
|
||||
}
|
||||
|
||||
KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword).run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
|
||||
sslKeystore.run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
|
||||
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)
|
||||
assertEquals(4, certificateChain.size)
|
||||
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { X509CertificateHolder(it.encoded).subject.commonName })
|
||||
}
|
||||
|
||||
trustStore.run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY))
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,5 +7,5 @@ Please refer to `README.md` in the individual project folders. There are the fo
|
||||
* **trader-demo** A simple driver for exercising the two party trading flow. In this scenario, a buyer wants to purchase some commercial paper by swapping his cash for commercial paper. The seller learns that the buyer exists, and sends them a message to kick off the trade. The seller, having obtained his CP, then quits and the buyer goes back to waiting. The buyer will sell as much CP as he can! **We recommend starting with this demo.**
|
||||
* **Network-visualiser** A tool that uses a simulation to visualise the interaction and messages between nodes on the Corda network. Currently only works for the IRS demo.
|
||||
* **simm-valudation-demo** A demo showing two nodes reaching agreement on the valuation of a derivatives portfolio.
|
||||
* **raft-notary-demo** A simple demonstration of a node getting multiple transactions notarised by a distributed (Raft-based) notary.
|
||||
* **bank-of-corda-demo** A demo showing a node acting as an issuer of fungible assets (initially Cash)
|
||||
* **notary-demo** A simple demonstration of a node getting multiple transactions notarised by a single or distributed (Raft or BFT SMaRt) notary.
|
||||
* **bank-of-corda-demo** A demo showing a node acting as an issuer of fungible assets (initially Cash)
|
||||
|
@ -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.
|
@ -41,7 +41,7 @@ publishing {
|
||||
publications {
|
||||
jarAndSources(MavenPublication) {
|
||||
from components.java
|
||||
artifactId 'raftnotarydemo'
|
||||
artifactId 'notarydemo'
|
||||
|
||||
artifact sourceJar
|
||||
artifact javadocJar
|
||||
@ -57,6 +57,10 @@ task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
|
||||
definitionClass = 'net.corda.notarydemo.RaftNotaryCordform'
|
||||
}
|
||||
|
||||
task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
|
||||
definitionClass = 'net.corda.notarydemo.BFTNotaryCordform'
|
||||
}
|
||||
|
||||
task notarise(type: JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = 'net.corda.notarydemo.NotariseKt'
|
@ -6,8 +6,6 @@ import net.corda.node.driver.driver
|
||||
import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformNode
|
||||
|
||||
fun CordformDefinition.node(configure: CordformNode.() -> Unit) = addNode { cordformNode -> cordformNode.configure() }
|
||||
|
||||
fun CordformDefinition.clean() {
|
||||
System.err.println("Deleting: $driverDirectory")
|
||||
driverDirectory.toFile().deleteRecursively()
|
@ -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() }
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ package net.corda.notarydemo
|
||||
import net.corda.demorun.clean
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
listOf(SingleNotaryCordform, RaftNotaryCordform).forEach {
|
||||
listOf(SingleNotaryCordform, RaftNotaryCordform, BFTNotaryCordform).forEach {
|
||||
it.clean()
|
||||
}
|
||||
}
|
@ -2,10 +2,12 @@ package net.corda.notarydemo
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.notUsed
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.map
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -14,11 +16,10 @@ import net.corda.notarydemo.flows.DummyIssueAndMove
|
||||
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val host = HostAndPort.fromString("localhost:10003")
|
||||
println("Connecting to the recipient node ($host)")
|
||||
CordaRPCClient(host).start("demo", "demo").use {
|
||||
val api = NotaryDemoClientApi(it.proxy)
|
||||
api.startNotarisation()
|
||||
val address = HostAndPort.fromParts("localhost", 10003)
|
||||
println("Connecting to the recipient node ($address)")
|
||||
CordaRPCClient(address).start(notaryDemoUser.username, notaryDemoUser.password).use {
|
||||
NotaryDemoClientApi(it.proxy).notarise(10)
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,34 +28,23 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
|
||||
private val notary by lazy {
|
||||
val (parties, partyUpdates) = rpc.networkMapUpdates()
|
||||
partyUpdates.notUsed()
|
||||
parties.first { it.advertisedServices.any { it.info.type.isNotary() } }.notaryIdentity
|
||||
parties.filter { it.advertisedServices.any { it.info.type.isNotary() } }.map { it.notaryIdentity }.distinct().single()
|
||||
}
|
||||
|
||||
private val counterpartyNode by lazy {
|
||||
val (parties, partyUpdates) = rpc.networkMapUpdates()
|
||||
partyUpdates.notUsed()
|
||||
parties.first { it.legalIdentity.name == BOB.name }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val TRANSACTION_COUNT = 10
|
||||
parties.single { it.legalIdentity.name == BOB.name }
|
||||
}
|
||||
|
||||
/** Makes calls to the node rpc to start transaction notarisation. */
|
||||
fun startNotarisation() {
|
||||
notarise(TRANSACTION_COUNT)
|
||||
}
|
||||
|
||||
fun notarise(count: Int) {
|
||||
println("Notary: \"${notary.name}\", with composite key: ${notary.owningKey.toStringShort()}")
|
||||
val transactions = buildTransactions(count)
|
||||
val signers = notariseTransactions(transactions)
|
||||
val transactionSigners = transactions.zip(signers).map {
|
||||
val (tx, signer) = it
|
||||
"Tx [${tx.tx.id.prefixChars()}..] signed by $signer"
|
||||
}.joinToString("\n")
|
||||
|
||||
println("Notary: \"${notary.name}\", with composite key: ${notary.owningKey.toStringShort()}\n" +
|
||||
"Notarised ${transactions.size} transactions:\n" + transactionSigners)
|
||||
println("Notarised ${transactions.size} transactions:")
|
||||
transactions.zip(notariseTransactions(transactions)).forEach { (tx, signersFuture) ->
|
||||
println("Tx [${tx.tx.id.prefixChars()}..] signed by ${signersFuture.getOrThrow().joinToString()}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,10 +53,9 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
|
||||
* as it consumes the original asset and creates a copy with the new owner as its output.
|
||||
*/
|
||||
private fun buildTransactions(count: Int): List<SignedTransaction> {
|
||||
val moveTransactions = (1..count).map {
|
||||
return Futures.allAsList((1..count).map {
|
||||
rpc.startFlow(::DummyIssueAndMove, notary, counterpartyNode.legalIdentity).returnValue
|
||||
}
|
||||
return Futures.allAsList(moveTransactions).getOrThrow()
|
||||
}).getOrThrow()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,10 +64,9 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
|
||||
*
|
||||
* @return a list of encoded signer public keys - one for every transaction
|
||||
*/
|
||||
private fun notariseTransactions(transactions: List<SignedTransaction>): List<String> {
|
||||
// TODO: Remove this suppress when we upgrade to kotlin 1.1 or when JetBrain fixes the bug.
|
||||
@Suppress("UNSUPPORTED_FEATURE")
|
||||
val signatureFutures = transactions.map { rpc.startFlow(::RPCStartableNotaryFlowClient, it).returnValue }
|
||||
return Futures.allAsList(signatureFutures).getOrThrow().map { it.map { it.by.toStringShort() }.joinToString() }
|
||||
private fun notariseTransactions(transactions: List<SignedTransaction>): List<ListenableFuture<List<String>>> {
|
||||
return transactions.map {
|
||||
rpc.startFlow(::RPCStartableNotaryFlowClient, it).returnValue.map { it.map { it.by.toStringShort() } }
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.core.utilities.BOB
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.demorun.node
|
||||
import net.corda.demorun.runNodes
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
@ -14,31 +13,30 @@ import net.corda.notarydemo.flows.DummyIssueAndMove
|
||||
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
|
||||
import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformContext
|
||||
import net.corda.demorun.util.*
|
||||
|
||||
fun main(args: Array<String>) = SingleNotaryCordform.runNodes()
|
||||
|
||||
val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>()))
|
||||
|
||||
object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", DUMMY_NOTARY.name) {
|
||||
init {
|
||||
node {
|
||||
name(ALICE.name.toString())
|
||||
nearestCity("London")
|
||||
name(ALICE.name)
|
||||
p2pPort(10002)
|
||||
rpcPort(10003)
|
||||
rpcUsers = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())).toMap())
|
||||
rpcUsers(notaryDemoUser)
|
||||
}
|
||||
node {
|
||||
name(BOB.name.toString())
|
||||
nearestCity("New York")
|
||||
name(BOB.name)
|
||||
p2pPort(10005)
|
||||
rpcPort(10006)
|
||||
}
|
||||
node {
|
||||
name(DUMMY_NOTARY.name.toString())
|
||||
nearestCity("London")
|
||||
advertisedServices = listOf(ServiceInfo(ValidatingNotaryService.type).toString())
|
||||
name(DUMMY_NOTARY.name)
|
||||
p2pPort(10009)
|
||||
rpcPort(10010)
|
||||
notaryNodePort(10008)
|
||||
advertisedServices(ServiceInfo(ValidatingNotaryService.type))
|
||||
}
|
||||
}
|
||||
|
@ -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!!)
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ include 'samples:trader-demo'
|
||||
include 'samples:irs-demo'
|
||||
include 'samples:network-visualiser'
|
||||
include 'samples:simm-valuation-demo'
|
||||
include 'samples:raft-notary-demo'
|
||||
include 'samples:notary-demo'
|
||||
include 'samples:bank-of-corda-demo'
|
||||
include 'cordform-common'
|
||||
include 'doorman'
|
||||
|
@ -189,7 +189,7 @@ fun testConfiguration(baseDirectory: Path, legalName: X500Name, basePort: Int):
|
||||
rpcAddress = HostAndPort.fromParts("localhost", basePort + 1),
|
||||
messagingServerAddress = null,
|
||||
extraAdvertisedServiceIds = emptyList(),
|
||||
notaryNodeId = null,
|
||||
bftReplicaId = null,
|
||||
notaryNodeAddress = null,
|
||||
notaryClusterAddresses = emptyList(),
|
||||
certificateChainCheckPolicies = emptyList(),
|
||||
@ -217,7 +217,6 @@ fun getTestX509Name(commonName: String): X500Name {
|
||||
val nameBuilder = X500NameBuilder(BCStyle.INSTANCE)
|
||||
nameBuilder.addRDN(BCStyle.CN, commonName)
|
||||
nameBuilder.addRDN(BCStyle.O, "R3")
|
||||
nameBuilder.addRDN(BCStyle.OU, "Corda QA Department")
|
||||
nameBuilder.addRDN(BCStyle.L, "New York")
|
||||
nameBuilder.addRDN(BCStyle.C, "US")
|
||||
return nameBuilder.build()
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.testing.messaging
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.nodeapi.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.ArtemisTcpTransport
|
||||
import net.corda.nodeapi.ConnectionDirection
|
||||
@ -37,6 +36,10 @@ class SimpleMQClient(val target: HostAndPort,
|
||||
fun createMessage(): ClientMessage = session.createMessage(false)
|
||||
|
||||
fun stop() {
|
||||
sessionFactory.close()
|
||||
try {
|
||||
sessionFactory.close()
|
||||
} catch (e: Exception) {
|
||||
// sessionFactory might not have initialised.
|
||||
}
|
||||
}
|
||||
}
|
@ -4,10 +4,12 @@ import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.appendToCommonName
|
||||
import net.corda.core.crypto.commonName
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.utilities.DUMMY_MAP
|
||||
import net.corda.core.utilities.WHITESPACE
|
||||
import net.corda.node.driver.addressMustNotBeBound
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
@ -107,7 +109,7 @@ abstract class NodeBasedTest {
|
||||
clusterSize: Int,
|
||||
serviceType: ServiceType = RaftValidatingNotaryService.type): ListenableFuture<List<Node>> {
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
(0 until clusterSize).map { tempFolder.root.toPath() / "${notaryName.commonName}-$it" },
|
||||
(0 until clusterSize).map { baseDirectory(notaryName.appendToCommonName("-$it")) },
|
||||
serviceType.id,
|
||||
notaryName)
|
||||
|
||||
@ -133,12 +135,14 @@ abstract class NodeBasedTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected fun baseDirectory(legalName: X500Name) = tempFolder.root.toPath() / legalName.commonName.replace(WHITESPACE, "")
|
||||
|
||||
private fun startNodeInternal(legalName: X500Name,
|
||||
platformVersion: Int,
|
||||
advertisedServices: Set<ServiceInfo>,
|
||||
rpcUsers: List<User>,
|
||||
configOverrides: Map<String, Any>): Node {
|
||||
val baseDirectory = (tempFolder.root.toPath() / legalName.commonName).createDirectories()
|
||||
val baseDirectory = baseDirectory(legalName).createDirectories()
|
||||
val localPort = getFreeLocalPorts("localhost", 2)
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory,
|
||||
|
@ -5,6 +5,7 @@ import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigParseOptions
|
||||
import net.corda.core.ErrorOr
|
||||
import net.corda.core.internal.addShutdownHook
|
||||
import net.corda.core.div
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.loggerFor
|
||||
@ -51,11 +52,11 @@ class Verifier {
|
||||
val session = sessionFactory.createSession(
|
||||
VerifierApi.VERIFIER_USERNAME, VerifierApi.VERIFIER_USERNAME, false, true, true, locator.isPreAcknowledge, locator.ackBatchSize
|
||||
)
|
||||
Runtime.getRuntime().addShutdownHook(Thread {
|
||||
addShutdownHook {
|
||||
log.info("Shutting down")
|
||||
session.close()
|
||||
sessionFactory.close()
|
||||
})
|
||||
}
|
||||
val consumer = session.createConsumer(VERIFICATION_REQUESTS_QUEUE_NAME)
|
||||
val replyProducer = session.createProducer()
|
||||
consumer.setMessageHandler {
|
||||
|
@ -79,7 +79,7 @@ class NodeWebServer(val config: WebServerConfig) {
|
||||
httpsConfiguration.outputBufferSize = 32768
|
||||
httpsConfiguration.addCustomizer(SecureRequestCustomizer())
|
||||
val sslContextFactory = SslContextFactory()
|
||||
sslContextFactory.keyStorePath = config.keyStoreFile.toString()
|
||||
sslContextFactory.keyStorePath = config.sslKeystore.toString()
|
||||
sslContextFactory.setKeyStorePassword(config.keyStorePassword)
|
||||
sslContextFactory.setKeyManagerPassword(config.keyStorePassword)
|
||||
sslContextFactory.setTrustStorePath(config.trustStoreFile.toString())
|
||||
|
Loading…
x
Reference in New Issue
Block a user