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