diff --git a/.ci/api-current.txt b/.ci/api-current.txt
index cab36647d2..5a07958353 100644
--- a/.ci/api-current.txt
+++ b/.ci/api-current.txt
@@ -1259,6 +1259,8 @@ public final class net.corda.core.crypto.Crypto extends java.lang.Object
@NotNull
public static final java.security.Provider findProvider(String)
@NotNull
+ public static final net.corda.core.crypto.SignatureScheme findSignatureScheme(int)
+ @NotNull
public static final net.corda.core.crypto.SignatureScheme findSignatureScheme(String)
@NotNull
public static final net.corda.core.crypto.SignatureScheme findSignatureScheme(java.security.PrivateKey)
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 9208ede561..988d6e4a25 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -106,6 +106,10 @@
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 3dd4b3feb0..8efe2c9314 100644
--- a/build.gradle
+++ b/build.gradle
@@ -167,6 +167,9 @@ allprojects {
suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml'
cveValidForHours = 1
format = 'ALL'
+ failOnError = project.getProperty('owasp.failOnError')
+ // by default CVSS is '11' which passes everything. Set between 0-10 to catch vulnerable deps
+ failBuildOnCVSS = project.getProperty('owasp.failBuildOnCVSS').toFloat()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml
index bb2bdbeb70..408d9574d5 100644
--- a/config/dev/log4j2.xml
+++ b/config/dev/log4j2.xml
@@ -12,7 +12,7 @@
-
+
-
+
diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
index 9da4417c7d..8f88762592 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
@@ -199,6 +199,12 @@ object Crypto {
+ signatureSchemeMap.values.map { Pair(it.signatureOID, it) })
.toMap()
+ /**
+ * Map of supported digital signature schemes associated by [SignatureScheme.schemeNumberID].
+ * SchemeNumberID is the scheme identifier attached to [SignatureMetadata].
+ */
+ private val signatureSchemeNumberIDMap: Map = Crypto.supportedSignatureSchemes().associateBy { it.schemeNumberID }
+
@JvmStatic
fun supportedSignatureSchemes(): List = ArrayList(signatureSchemeMap.values)
@@ -224,6 +230,13 @@ object Crypto {
?: throw IllegalArgumentException("Unrecognised algorithm: ${algorithm.algorithm.id}")
}
+ /** Find [SignatureScheme] by platform specific schemeNumberID. */
+ @JvmStatic
+ fun findSignatureScheme(schemeNumberID: Int): SignatureScheme {
+ return signatureSchemeNumberIDMap[schemeNumberID]
+ ?: throw IllegalArgumentException("Unsupported key/algorithm for schemeNumberID: $schemeNumberID")
+ }
+
/**
* Factory pattern to retrieve the corresponding [SignatureScheme] based on [SignatureScheme.schemeCodeName].
* This function is usually called by key generators and verify signature functions.
@@ -458,9 +471,14 @@ object Crypto {
@JvmStatic
@Throws(InvalidKeyException::class, SignatureException::class)
fun doSign(keyPair: KeyPair, signableData: SignableData): TransactionSignature {
- val sigKey: SignatureScheme = findSignatureScheme(keyPair.private)
- val sigMetaData: SignatureScheme = findSignatureScheme(keyPair.public)
- require(sigKey == sigMetaData) {
+ val sigKey: SignatureScheme = Crypto.findSignatureScheme(keyPair.private)
+ val sigMetaData: SignatureScheme = Crypto.findSignatureScheme(signableData.signatureMetadata.schemeNumberID)
+ // Special handling if the advertised SignatureScheme is CompositeKey.
+ // TODO fix notaries that advertise [CompositeKey] in their signature Metadata. Currently, clustered notary nodes
+ // mention Crypto.COMPOSITE_KEY in their SignatureMetadata, but they are actually signing with a leaf-key
+ // (and if they refer to it as a Composite key, then we lose info about the actual type of their signing key).
+ // In short, their metadata should be the leaf key-type, until we support CompositeKey signatures.
+ require(sigKey == sigMetaData || sigMetaData == Crypto.COMPOSITE_KEY) {
"Metadata schemeCodeName: ${sigMetaData.schemeCodeName} is not aligned with the key type: ${sigKey.schemeCodeName}."
}
val signatureBytes = doSign(sigKey.schemeCodeName, keyPair.private, signableData.serialize().bytes)
diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/AliasPrivateKey.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/AliasPrivateKey.kt
new file mode 100644
index 0000000000..50d75fdb31
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/crypto/internal/AliasPrivateKey.kt
@@ -0,0 +1,39 @@
+package net.corda.core.crypto.internal
+
+import org.bouncycastle.asn1.*
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier
+import java.security.KeyPair
+import java.security.PrivateKey
+import java.security.spec.PKCS8EncodedKeySpec
+
+/**
+ * [PrivateKey] wrapper to just store the alias of a private key.
+ * Usually, HSM (hardware secure module) key entries are accessed via unique aliases and the private key material never
+ * leaves the box. This class wraps a [String] key alias into a [PrivateKey] object, which helps on transferring
+ * [KeyPair] objects without exposing the private key material. Then, whenever we need to sign with the actual private
+ * key, we provide the [alias] from this [AliasPrivateKey] to the underlying HSM implementation.
+ */
+data class AliasPrivateKey(val alias: String): PrivateKey {
+
+ companion object {
+ // UUID-based OID
+ // TODO: Register for an OID space and issue our own shorter OID.
+ @JvmField
+ val ALIAS_PRIVATE_KEY = ASN1ObjectIdentifier("2.26.40086077608615255153862931087626791001")
+
+ const val ALIAS_KEY_ALGORITHM = "ALIAS"
+ }
+
+ override fun getAlgorithm() = ALIAS_KEY_ALGORITHM
+
+ override fun getEncoded(): ByteArray {
+ val keyVector = ASN1EncodableVector()
+ keyVector.add(DERUTF8String(alias))
+ val privateKeyInfoBytes = PrivateKeyInfo(AlgorithmIdentifier(ALIAS_PRIVATE_KEY), DERSequence(keyVector)).getEncoded(ASN1Encoding.DER)
+ val keySpec = PKCS8EncodedKeySpec(privateKeyInfoBytes)
+ return keySpec.encoded
+ }
+
+ override fun getFormat() = "PKCS#8"
+}
diff --git a/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt b/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt
index 17d2ff5b65..9ba4c1adb8 100644
--- a/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt
@@ -14,9 +14,10 @@ object JarSignatureCollector {
/**
* @see
- * also accepting *.EC as this can be created and accepted by jarsigner tool @see https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html
- * and Java Security Manager. */
- private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA|EC)|SIG-.*)".toRegex()
+ * Additionally accepting *.EC as its valid for [java.util.jar.JarVerifier] and jarsigner @see https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html,
+ * temporally treating META-INF/INDEX.LIST as unsignable entry because [java.util.jar.JarVerifier] doesn't load its signers.
+ */
+ private val unsignableEntryName = "META-INF/(?:(?:.*[.](?:SF|DSA|RSA|EC)|SIG-.*)|INDEX\\.LIST)".toRegex()
/**
* Returns an ordered list of every [Party] which has signed every signable item in the given [JarInputStream].
diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt
index aec3891862..2c1c807633 100644
--- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt
+++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt
@@ -86,7 +86,6 @@ data class NetworkParameters(
require(epoch > 0) { "epoch must be at least 1" }
require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
- require(maxTransactionSize <= maxMessageSize) { "maxTransactionSize cannot be bigger than maxMessageSize" }
require(!eventHorizon.isNegative) { "eventHorizon must be positive value" }
require(noOverlap(packageOwnership.keys)) { "multiple packages added to the packageOwnership overlap." }
}
diff --git a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt
index 30adb39244..cd3a5a511e 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt
@@ -21,18 +21,24 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
+import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECKey
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec
+import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.junit.Assert.assertNotEquals
import org.junit.Test
import java.math.BigInteger
+import java.security.KeyPair
import java.security.KeyPairGenerator
+import java.security.cert.X509Certificate
import java.util.*
import kotlin.test.*
@@ -936,4 +942,32 @@ class CryptoUtilsTest {
// Just in case, test that signatures of different messages are not the same.
assertNotEquals(OpaqueBytes(signedData1stTime), OpaqueBytes(signedZeroArray1stTime))
}
+
+ fun ContentSigner.write(message: ByteArray) {
+ this.outputStream.write(message)
+ this.outputStream.close()
+ }
+
+ private fun createCert(signer: ContentSigner, keyPair: KeyPair): X509Certificate {
+ val dname = X500Name("CN=TestEntity")
+ val startDate = Calendar.getInstance().let { cal ->
+ cal.time = Date()
+ cal.add(Calendar.HOUR, -1)
+ cal.time
+ }
+ val endDate = Calendar.getInstance().let { cal ->
+ cal.time = startDate
+ cal.add(Calendar.YEAR, 1)
+ cal.time
+ }
+ val certificate = JcaX509v3CertificateBuilder(
+ dname,
+ BigInteger.TEN,
+ startDate,
+ endDate,
+ dname,
+ keyPair.public
+ ).build(signer)
+ return JcaX509CertificateConverter().getCertificate(certificate)
+ }
}
diff --git a/gradle.properties b/gradle.properties
index 0c6bf2d49f..3e359435f6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,5 @@
kotlin.incremental=true
org.gradle.jvmargs=-XX:+UseG1GC -Xmx1g -Dfile.encoding=UTF-8
org.gradle.caching=true
+owasp.failOnError=false
+owasp.failBuildOnCVSS=11.0
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt
index 632e2ad313..57c5ff659b 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt
@@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.config
+import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.internal.outputStream
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
@@ -8,6 +9,7 @@ import java.io.InputStream
import java.io.OutputStream
import java.nio.file.OpenOption
import java.nio.file.Path
+import java.security.PrivateKey
import java.security.cert.X509Certificate
interface CertificateStore : Iterable> {
@@ -42,7 +44,6 @@ interface CertificateStore : Iterable> {
}
operator fun set(alias: String, certificate: X509Certificate) {
-
update {
internal.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, certificate)
}
@@ -65,7 +66,6 @@ interface CertificateStore : Iterable> {
* @throws IllegalArgumentException if no certificate for the alias is found, or if the certificate is not an [X509Certificate].
*/
operator fun get(alias: String): X509Certificate {
-
return query {
getCertificate(alias)
}
@@ -79,6 +79,21 @@ interface CertificateStore : Iterable> {
this@CertificateStore.forEach(::setCertificate)
}
}
+
+ fun setCertPathOnly(alias: String, certificates: List) {
+ // In case CryptoService and CertificateStore share the same KeyStore (i.e., when BCCryptoService is used),
+ // extract the existing key from the Keystore and store it again along with the new certificate chain.
+ // This is because KeyStores do not support updateKeyEntry and thus we cannot update the certificate chain
+ // without overriding the key entry.
+ // Note that if the given alias already exists, the keystore information associated with it
+ // is overridden by the given key (and associated certificate chain).
+ val privateKey: PrivateKey = if (this.contains(alias)) {
+ this.value.getPrivateKey(alias, entryPassword)
+ } else {
+ AliasPrivateKey(alias)
+ }
+ this.value.setPrivateKey(alias, privateKey, certificates, entryPassword)
+ }
}
private class DelegatingCertificateStore(override val value: X509KeyStore, override val password: String, override val entryPassword: String) : CertificateStore
\ No newline at end of file
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStoreSupplier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStoreSupplier.kt
index 7cdfeee79b..f56d2f52d1 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStoreSupplier.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStoreSupplier.kt
@@ -19,6 +19,5 @@ interface CertificateStoreSupplier {
// TODO replace reference to FileBasedCertificateStoreSupplier with CertificateStoreSupplier, after coming up with a way of passing certificate stores to Artemis.
class FileBasedCertificateStoreSupplier(val path: Path, val storePassword: String, val entryPassword: String) : CertificateStoreSupplier {
-
override fun get(createNew: Boolean) = CertificateStore.fromFile(path, storePassword, entryPassword, createNew)
-}
\ No newline at end of file
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt
index dad42d37ca..f877f6ffd2 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt
@@ -16,7 +16,6 @@ interface SslConfiguration {
}
interface MutualSslConfiguration : SslConfiguration {
-
override val keyStore: FileBasedCertificateStoreSupplier
override val trustStore: FileBasedCertificateStoreSupplier
}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt
index bfcf1f6631..01ce3e5b51 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt
@@ -10,8 +10,8 @@ import java.security.SecureRandom
import java.security.Signature
/**
- * Provide extra OID look up for signature algorithm not supported by bouncy castle.
- * This builder will use bouncy castle's JcaContentSignerBuilder as fallback for unknown algorithm.
+ * Provide extra OID look up for signature algorithm not supported by BouncyCastle.
+ * This builder will use BouncyCastle's JcaContentSignerBuilder as fallback for unknown algorithm.
*/
object ContentSignerBuilder {
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner {
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.kt
new file mode 100644
index 0000000000..77062a6bc0
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.kt
@@ -0,0 +1,14 @@
+package net.corda.nodeapi.internal.crypto
+
+import net.corda.core.crypto.Crypto
+import net.corda.nodeapi.internal.crypto.X509Utilities.createSelfSignedCACertificate
+import java.math.BigInteger
+import javax.security.auth.x500.X500Principal
+
+/**
+ * Dummy keys and certificates mainly required when we need to store dummy entries to KeyStores, i.e., as progress
+ * indicators in node registration. */
+object NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS {
+ val ECDSAR1_KEYPAIR by lazy { Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger.valueOf(0)) }
+ val ECDSAR1_CERT by lazy { createSelfSignedCACertificate(X500Principal("CN=DUMMY"), ECDSAR1_KEYPAIR) }
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt
index f039af0b3d..04703466f6 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt
@@ -7,14 +7,15 @@ import java.nio.file.Path
import java.security.KeyPair
import java.security.KeyStore
import java.security.PrivateKey
+import java.security.PublicKey
import java.security.cert.X509Certificate
/**
* Wrapper around a [KeyStore] object but only dealing with [X509Certificate]s and with a better API.
*/
-class X509KeyStore private constructor(val internal: KeyStore, private val storePassword: String, private val keyStoreFile: Path? = null) {
+class X509KeyStore private constructor(val internal: KeyStore, private val storePassword: String, private val keyStoreFile: Path? = null, private val saveSupported: Boolean = true) {
/** Wrap an existing [KeyStore]. [save] is not supported. */
- constructor(keyStore: KeyStore, storePassword: String) : this(keyStore, storePassword, null)
+ constructor(keyStore: KeyStore, storePassword: String) : this(keyStore, storePassword, null, false)
/** Create an empty [KeyStore] using the given password. [save] is not supported. */
constructor(storePassword: String) : this(
@@ -55,24 +56,32 @@ class X509KeyStore private constructor(val internal: KeyStore, private val store
fun getCertificateAndKeyPair(alias: String, keyPassword: String): CertificateAndKeyPair {
val cert = getCertificate(alias)
- val publicKey = Crypto.toSupportedPublicKey(cert.publicKey)
+ val publicKey = getPublicKey(alias)
return CertificateAndKeyPair(cert, KeyPair(publicKey, getPrivateKey(alias, keyPassword)))
}
+ fun getPublicKey(alias: String): PublicKey {
+ return Crypto.toSupportedPublicKey(getCertificate(alias).publicKey)
+ }
+
fun getPrivateKey(alias: String, keyPassword: String): PrivateKey {
return internal.getSupportedKey(alias, keyPassword)
}
fun setPrivateKey(alias: String, key: PrivateKey, certificates: List, keyPassword: String) {
internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray())
+ save()
}
fun setCertificate(alias: String, certificate: X509Certificate) {
internal.setCertificateEntry(alias, certificate)
+ save()
}
fun save() {
- internal.save(checkWritableToFile(), storePassword)
+ if (saveSupported) {
+ internal.save(checkWritableToFile(), storePassword)
+ }
}
fun update(action: X509KeyStore.() -> Unit) {
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt
index dafa767440..9a21bfac72 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt
@@ -301,24 +301,21 @@ object X509Utilities {
/**
* Create certificate signing request using provided information.
*/
- private fun createCertificateSigningRequest(subject: X500Principal,
- email: String,
- keyPair: KeyPair,
- signatureScheme: SignatureScheme,
- certRole: CertRole): PKCS10CertificationRequest {
- val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.findProvider(signatureScheme.providerName))
- return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public)
+ fun createCertificateSigningRequest(subject: X500Principal, email: String, publicKey: PublicKey, contentSigner: ContentSigner, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest {
+ return JcaPKCS10CertificationRequestBuilder(subject, publicKey)
.addAttribute(BCStyle.E, DERUTF8String(email))
.addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole)
- .build(signer).apply {
- if (!isSignatureValid()) {
- throw SignatureException("The certificate signing request signature validation failed.")
- }
- }
+ .build(contentSigner).apply {
+ if (!isSignatureValid()) {
+ throw SignatureException("The certificate signing request signature validation failed.")
+ }
+ }
}
fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest {
- return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME, certRole)
+ val signatureScheme = Crypto.findSignatureScheme(keyPair.public)
+ val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.findProvider(signatureScheme.providerName))
+ return createCertificateSigningRequest(subject, email, keyPair.public, signer, certRole)
}
fun buildCertPath(first: X509Certificate, remaining: List): CertPath {
@@ -356,7 +353,7 @@ object X509Utilities {
val CertRole.certificateType: CertificateType get() = CertificateType.values().first { it.role == this }
/**
- * Convert a [X509Certificate] into Bouncycastle's [X509CertificateHolder].
+ * Convert a [X509Certificate] into BouncyCastle's [X509CertificateHolder].
*
* NOTE: To avoid unnecessary copying use [X509Certificate] where possible.
*/
@@ -376,7 +373,7 @@ val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certif
val Array.x509: List get() = map { it.x509 }
/**
- * Validates the signature of the CSR
+ * Validates the signature of the CSR.
*/
fun PKCS10CertificationRequest.isSignatureValid(): Boolean {
return this.isSignatureValid(JcaContentVerifierProviderBuilder().build(this.subjectPublicKeyInfo))
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/CryptoService.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/CryptoService.kt
new file mode 100644
index 0000000000..e24b53c39d
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/CryptoService.kt
@@ -0,0 +1,40 @@
+package net.corda.nodeapi.internal.cryptoservice
+
+import net.corda.core.DoNotImplement
+import org.bouncycastle.operator.ContentSigner
+import java.security.KeyPair
+import java.security.PublicKey
+
+@DoNotImplement
+interface CryptoService {
+
+ /**
+ * Generate and store a new [KeyPair].
+ * Note that schemeNumberID is Corda specific. Cross-check with the network operator for supported schemeNumberID
+ * and their corresponding signature schemes. The main reason for using schemeNumberID and not algorithm OIDs is
+ * because some schemes might not be standardised and thus an official OID might for this scheme not exist yet.
+ *
+ * Returns the [PublicKey] of the generated [KeyPair].
+ */
+ fun generateKeyPair(alias: String, schemeNumberID: Int): PublicKey
+
+ /** Check if this [CryptoService] has a private key entry for the input alias. */
+ fun containsKey(alias: String): Boolean
+
+ /**
+ * Returns the [PublicKey] of the input alias or null if it doesn't exist.
+ */
+ fun getPublicKey(alias: String): PublicKey?
+
+ /**
+ * Sign a [ByteArray] using the private key identified by the input alias.
+ * Returns the signature bytes whose format depends on the underlying signature scheme and it should
+ * be Java BouncyCastle compatible (i.e., ASN.1 DER-encoded for ECDSA).
+ */
+ fun sign(alias: String, data: ByteArray): ByteArray
+
+ /**
+ * Returns [ContentSigner] for the key identified by the input alias.
+ */
+ fun getSigner(alias: String): ContentSigner
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
index db539a2c4d..ff94acacae 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
@@ -394,7 +394,7 @@ internal constructor(private val initSerEnv: Boolean,
notaries = notaryInfos,
modifiedTime = Instant.now(),
maxMessageSize = 10485760,
- maxTransactionSize = 10485760,
+ maxTransactionSize = 524288000,
whitelistedContractImplementations = whitelist,
packageOwnership = packageOwnership.filterNotNullValues(),
epoch = 1,
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/AliasPrivateKeyTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/AliasPrivateKeyTest.kt
new file mode 100644
index 0000000000..93bc1e24f4
--- /dev/null
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/AliasPrivateKeyTest.kt
@@ -0,0 +1,34 @@
+package net.corda.nodeapi.internal.crypto
+
+import net.corda.core.crypto.internal.AliasPrivateKey
+import net.corda.testing.internal.stubs.CertificateStoreStubs
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+
+class AliasPrivateKeyTest {
+
+ @Rule
+ @JvmField
+ val tempFolder = TemporaryFolder()
+
+ @Test
+ fun `store AliasPrivateKey entry and cert to keystore`() {
+ val alias = "01234567890"
+ val aliasPrivateKey = AliasPrivateKey(alias)
+ val certificatesDirectory = tempFolder.root.toPath()
+ val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "keystorepass").get(createNew = true)
+ signingCertStore.query { setPrivateKey(alias, aliasPrivateKey, listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), "entrypassword") }
+ // We can retrieve the certificate.
+ assertTrue { signingCertStore.contains(alias) }
+ // We can retrieve the certificate.
+ assertEquals(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT, signingCertStore[alias])
+ // Although we can store an AliasPrivateKey, we cannot retrieve it. But, it's fine as we use certStore for storing/handling certs only.
+ assertFailsWith("Unrecognised algorithm: 2.26.40086077608615255153862931087626791001") {
+ signingCertStore.query { getPrivateKey(alias, "entrypassword") }
+ }
+ }
+}
diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt
index 2a10fa56c5..47a290f158 100644
--- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt
@@ -95,7 +95,7 @@ class NodeRegistrationTest : IntegrationTest() {
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"),
notaryCustomOverrides = mapOf("devMode" to false)
) {
- val (alice, genevieve) = listOf(
+ val (alice, genevieve) = listOf(
startNode(providedName = aliceName, customOverrides = mapOf("devMode" to false)),
startNode(providedName = genevieveName, customOverrides = mapOf("devMode" to false))
).transpose().getOrThrow()
diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
index f07e5024f0..aa619cb5bc 100644
--- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
@@ -9,22 +9,20 @@ import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext
+import net.corda.core.crypto.internal.AliasPrivateKey
+import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isCRLDistributionPointBlacklisted
import net.corda.core.crypto.newSecureRandom
-import net.corda.core.crypto.sign
import net.corda.core.flows.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
-import net.corda.core.internal.FlowStateMachine
-import net.corda.core.internal.NamedCacheFactory
-import net.corda.core.internal.VisibleForTesting
+import net.corda.core.internal.*
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.notary.NotaryService
-import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.*
import net.corda.core.node.*
import net.corda.core.node.services.*
@@ -54,8 +52,9 @@ import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService
+import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.keys.KeyManagementServiceInternal
-import net.corda.node.services.keys.PersistentKeyManagementService
+import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.node.services.messaging.DeduplicationHandler
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.NetworkMapClient
@@ -73,14 +72,15 @@ import net.corda.node.utilities.*
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.CertificateStore
+import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
+import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX
import net.corda.nodeapi.internal.persistence.*
-import net.corda.nodeapi.internal.storeLegalIdentity
import net.corda.tools.shell.InteractiveShell
import org.apache.activemq.artemis.utils.ReusableLatch
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
@@ -165,6 +165,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize()
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
+ val cryptoService = configuration.makeCryptoService()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
@@ -260,6 +261,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
private fun initKeyStores(): X509Certificate {
if (configuration.devMode) {
configuration.configureWithDevSSLCertificate()
+ // configureWithDevSSLCertificate is a devMode process that writes directly to keystore files, so
+ // we should re-synchronise BCCryptoService with the updated keystore file.
+ if (cryptoService is BCCryptoService) {
+ cryptoService.resyncKeystore()
+ }
}
return validateKeyStores()
}
@@ -298,7 +304,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
log.info("Node starting up ...")
val trustRoot = initKeyStores()
- val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
initialiseJVMAgents()
schemaService.mappedSchemasWarnings().forEach {
@@ -324,6 +329,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
startDatabase()
val (identity, identityKeyPair) = obtainIdentity()
+ X509Utilities.validateCertPath(trustRoot, identity.certPath)
+
+ val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
val mutualExclusionConfiguration = configuration.enterpriseConfiguration.mutualExclusionConfiguration
@@ -357,6 +365,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
attachments.start()
cordappProvider.start(netParams.whitelistedContractImplementations)
nodeProperties.start()
+ // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
+ // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
+ // the identity key. But the infrastructure to make that easy isn't here yet.
keyManagementService.start(keyPairs)
val notaryService = makeNotaryService(myNotaryIdentity)
installCordaServices(myNotaryIdentity)
@@ -434,7 +445,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val nodeInfoFromDb = getPreviousNodeInfoIfPresent(identity)
-
val nodeInfo = if (potentialNodeInfo == nodeInfoFromDb?.copy(serial = 0)) {
// The node info hasn't changed. We use the one from the database to preserve the serial.
log.debug("Node-info hasn't changed")
@@ -449,7 +459,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val nodeInfoAndSigned = NodeInfoAndSigned(nodeInfo) { publicKey, serialised ->
val privateKey = keyPairs.single { it.public == publicKey }.private
- privateKey.sign(serialised.bytes)
+ DigitalSignature(cryptoService.sign((privateKey as AliasPrivateKey).alias, serialised.bytes))
}
// Write the node-info file even if nothing's changed, just in case the file has been deleted.
@@ -558,7 +568,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " +
ServiceHub::class.java.name)
} catch (e: ServiceInstantiationException) {
- log.error("Corda service ${it.name} failed to instantiate", e.cause)
+ if (e.cause != null) {
+ log.error("Corda service ${it.name} failed to instantiate. Reason was: ${e.cause?.rootMessage}", e.cause)
+ } else {
+ log.error("Corda service ${it.name} failed to instantiate", e)
+ }
} catch (e: Exception) {
log.error("Unable to install Corda service ${it.name}", e)
}
@@ -664,9 +678,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return try {
// The following will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeyStore = configuration.p2pSslOptions.keyStore.get()
- val identitiesKeyStore = configuration.signingCertificateStore.get()
+ val signingCertificateStore = configuration.signingCertificateStore.get()
val trustStore = configuration.p2pSslOptions.trustStore.get()
- AllCertificateStores(trustStore, sslKeyStore, identitiesKeyStore)
+ AllCertificateStores(trustStore, sslKeyStore, signingCertificateStore)
} catch (e: IOException) {
log.error("IO exception while trying to validate keystores and truststore", e)
null
@@ -788,7 +802,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet.
- return PersistentKeyManagementService(cacheFactory, identityService, database)
+ return BasicHSMKeyManagementService(cacheFactory,identityService, database, cryptoService)
}
open fun stop() {
@@ -813,50 +827,47 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
nodeInfo: NodeInfo,
myNotaryIdentity: PartyAndCertificate?,
networkParameters: NetworkParameters)
-
- /** Loads or generates the node's legal identity and key-pair. */
+ /**
+ * Loads or generates the node's legal identity and key-pair.
+ * Note that obtainIdentity returns a KeyPair with an [AliasPrivateKey].
+ */
private fun obtainIdentity(): Pair {
- val keyStore = configuration.signingCertificateStore.get()
- val legalName = configuration.myLegalName
+ val legalIdentityPrivateKeyAlias = "$NODE_IDENTITY_ALIAS_PREFIX-private-key"
- // TODO: Integrate with Key management service?
- val privateKeyAlias = "$NODE_IDENTITY_ALIAS_PREFIX-private-key"
- if (privateKeyAlias !in keyStore) {
- log.info("$privateKeyAlias not found in key store, generating fresh key!")
- keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair())
+ if (!cryptoService.containsKey(legalIdentityPrivateKeyAlias)) {
+ log.info("$legalIdentityPrivateKeyAlias not found in key store, generating fresh key!")
+ storeLegalIdentity(legalIdentityPrivateKeyAlias)
}
-
- val (x509Cert, keyPair) = keyStore.query { getCertificateAndKeyPair(privateKeyAlias, keyStore.entryPassword) }
+ val signingCertificateStore = configuration.signingCertificateStore.get()
+ val x509Cert = signingCertificateStore.query { getCertificate(legalIdentityPrivateKeyAlias) }
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
- val certificates = keyStore.query { getCertificateChain(privateKeyAlias) }
+ val certificates: List = signingCertificateStore.query { getCertificateChain(legalIdentityPrivateKeyAlias) }
check(certificates.first() == x509Cert) {
"Certificates from key store do not line up!"
}
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
+ val legalName = configuration.myLegalName
if (subject != legalName) {
throw ConfigurationException("The name '$legalName' for $NODE_IDENTITY_ALIAS_PREFIX doesn't match what's in the key store: $subject")
}
- val certPath = X509Utilities.buildCertPath(certificates)
- return Pair(PartyAndCertificate(certPath), keyPair)
+ return getPartyAndCertificatePlusAliasKeyPair(certificates, legalIdentityPrivateKeyAlias)
}
/** Loads pre-generated notary service cluster identity. */
private fun loadNotaryClusterIdentity(serviceLegalName: CordaX500Name): Pair {
- val keyStore = configuration.signingCertificateStore.get()
-
val privateKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key"
- val keyPair = keyStore.query { getCertificateAndKeyPair(privateKeyAlias, keyStore.entryPassword) }.keyPair
-
val compositeKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key"
- val certificates = if (compositeKeyAlias in keyStore) {
- val certificate = keyStore[compositeKeyAlias]
+
+ val signingCertificateStore = configuration.signingCertificateStore.get()
+ val certificates = if (cryptoService.containsKey(compositeKeyAlias)) {
+ val certificate = signingCertificateStore[compositeKeyAlias]
// We have to create the certificate chain for the composite key manually, this is because we don't have a keystore
// provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate +
// the tail of the private key certificates, as they are both signed by the same certificate chain.
- listOf(certificate) + keyStore.query { getCertificateChain(privateKeyAlias) }.drop(1)
+ listOf(certificate) + signingCertificateStore.query { getCertificateChain(privateKeyAlias) }.drop(1)
} else throw IllegalStateException("The identity public key for the notary service $serviceLegalName was not found in the key store.")
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
@@ -864,11 +875,43 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
throw ConfigurationException("The name of the notary service '$serviceLegalName' for $DISTRIBUTED_NOTARY_ALIAS_PREFIX doesn't " +
"match what's in the key store: $subject. You might need to adjust the configuration of `notary.serviceLegalName`.")
}
+ return getPartyAndCertificatePlusAliasKeyPair(certificates, privateKeyAlias)
+ }
+
+ // Method to create a Pair, where KeyPair uses an AliasPrivateKey.
+ private fun getPartyAndCertificatePlusAliasKeyPair(certificates: List, privateKeyAlias: String): Pair {
val certPath = X509Utilities.buildCertPath(certificates)
+ val keyPair = KeyPair(cryptoService.getPublicKey(privateKeyAlias), AliasPrivateKey(privateKeyAlias))
return Pair(PartyAndCertificate(certPath), keyPair)
}
- protected open fun generateKeyPair() = cryptoGenerateKeyPair()
+ private fun storeLegalIdentity(alias: String): PartyAndCertificate {
+ val legalIdentityPublicKey = generateKeyPair(alias)
+ val signingCertificateStore = configuration.signingCertificateStore.get()
+
+ val nodeCaCertPath = signingCertificateStore.value.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
+ val nodeCaCert = nodeCaCertPath[0] // This should be the same with signingCertificateStore[alias]
+
+ val identityCert = X509Utilities.createCertificate(
+ CertificateType.LEGAL_IDENTITY,
+ nodeCaCert.subjectX500Principal,
+ nodeCaCert.publicKey,
+ cryptoService.getSigner(X509Utilities.CORDA_CLIENT_CA),
+ nodeCaCert.subjectX500Principal,
+ legalIdentityPublicKey,
+ // TODO this might be smaller than DEFAULT_VALIDITY_WINDOW, shall we strictly apply DEFAULT_VALIDITY_WINDOW?
+ X509Utilities.getCertificateValidityWindow(
+ DEFAULT_VALIDITY_WINDOW.first,
+ DEFAULT_VALIDITY_WINDOW.second,
+ nodeCaCert)
+ )
+
+ val identityCertPath = listOf(identityCert) + nodeCaCertPath
+ signingCertificateStore.setCertPathOnly(alias, identityCertPath)
+ return PartyAndCertificate(X509Utilities.buildCertPath(identityCertPath))
+ }
+
+ protected open fun generateKeyPair(alias: String) = cryptoService.generateKeyPair(alias, X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME.schemeNumberID)
protected open fun makeVaultService(keyManagementService: KeyManagementService,
services: ServicesForResolution,
diff --git a/node/src/main/kotlin/net/corda/node/internal/subcommands/InitialRegistrationCli.kt b/node/src/main/kotlin/net/corda/node/internal/subcommands/InitialRegistrationCli.kt
index 2f550be957..ca046ea199 100644
--- a/node/src/main/kotlin/net/corda/node/internal/subcommands/InitialRegistrationCli.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/subcommands/InitialRegistrationCli.kt
@@ -71,7 +71,7 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
HTTPNetworkRegistrationService(
requireNotNull(conf.networkServices),
versionInfo),
- nodeRegistration).buildKeystore()
+ nodeRegistration).generateKeysAndRegister()
// Minimal changes to make registration tool create node identity.
// TODO: Move node identity generation logic from node to registration helper.
@@ -107,4 +107,3 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
initialRegistration(node.configuration)
}
}
-
diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt
index 95b4e27c44..e31d46626e 100644
--- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt
+++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt
@@ -82,10 +82,11 @@ object ConfigHelper {
* Strictly for dev only automatically construct a server certificate/private key signed from
* the CA certs in Node resources. Then provision KeyStores into certificates folder under node path.
*/
-// TODO Move this to KeyStoreConfigHelpers
+// TODO Move this to KeyStoreConfigHelpers.
+// TODO consider taking CryptoService as an input.
fun NodeConfiguration.configureWithDevSSLCertificate() = p2pSslOptions.configureDevKeyAndTrustStores(myLegalName, signingCertificateStore, certificatesDirectory)
-// TODO Move this to KeyStoreConfigHelpers
+// TODO Move this to KeyStoreConfigHelpers.
fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path) {
val specifiedTrustStore = trustStore.getOptional()
@@ -100,7 +101,7 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
loadDevCaTrustStore().copyTo(trustStore.get(true))
}
- if (keyStore.getOptional() == null || signingCertificateStore.getOptional() == null) {
+ if (specifiedKeyStore == null || specifiedSigningStore == null) {
val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.storePassword, signingCertificateStore.entryPassword).get(true).also { it.registerDevSigningCertificates(myLegalName) }
FileBasedCertificateStoreSupplier(keyStore.path, keyStore.storePassword, keyStore.entryPassword).get(true).also { it.registerDevP2pCertificates(myLegalName) }
diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
index 6e57e50669..25727f6695 100644
--- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
+++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
@@ -10,11 +10,14 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import net.corda.node.services.config.rpc.NodeRpcOptions
+import net.corda.node.services.keys.cryptoservice.BCCryptoService
+import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
import net.corda.nodeapi.BrokerRpcSslOptions
+import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.nodeapi.internal.config.*
+import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag
import net.corda.nodeapi.internal.persistence.DatabaseConfig
-import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.tools.shell.SSHDConfiguration
import org.slf4j.Logger
import java.net.URL
@@ -79,6 +82,8 @@ interface NodeConfiguration {
val baseDirectory: Path
val certificatesDirectory: Path
+ // signingCertificateStore is used to store certificate chains.
+ // However, BCCryptoService is reusing this to store keys as well.
val signingCertificateStore: FileBasedCertificateStoreSupplier
val p2pSslOptions: MutualSslConfiguration
@@ -88,6 +93,11 @@ interface NodeConfiguration {
val cordappSignerKeyFingerprintBlacklist: List
+ // TODO At the moment this is just an identifier for the desired CryptoService engine. Consider using a classname to
+ // to allow for pluggable implementations.
+ val cryptoServiceName: SupportedCryptoServices?
+ val cryptoServiceConf: String? // Location for the cryptoService conf file.
+
fun validate(): List
companion object {
@@ -106,6 +116,13 @@ interface NodeConfiguration {
val defaultJmxReporterType = JmxReporterType.JOLOKIA
}
+
+ fun makeCryptoService(): CryptoService {
+ return when(cryptoServiceName) {
+ SupportedCryptoServices.BC_SIMPLE -> BCCryptoService(this)
+ null -> BCCryptoService(this) // Pick default BCCryptoService when null.
+ }
+ }
}
data class FlowOverrideConfig(val overrides: List = listOf())
@@ -240,11 +257,13 @@ data class NodeConfigurationImpl(
override val enableSNI: Boolean = true,
private val useOpenSsl: Boolean = false,
override val flowOverrides: FlowOverrideConfig?,
- override val cordappSignerKeyFingerprintBlacklist: List = DEV_PUB_KEY_HASHES.map { it.toString() }
+ override val cordappSignerKeyFingerprintBlacklist: List = DEV_PUB_KEY_HASHES.map { it.toString() },
+ override val cryptoServiceName: SupportedCryptoServices? = null,
+ override val cryptoServiceConf: String? = null
) : NodeConfiguration {
companion object {
private val logger = loggerFor()
-
+ // private val supportedCryptoServiceNames = setOf("BC", "UTIMACO", "GEMALTO-LUNA", "AZURE-KEY-VAULT")
}
private val actualRpcSettings: NodeRpcSettings
@@ -297,6 +316,14 @@ data class NodeConfigurationImpl(
return errors
}
+ private fun validateCryptoService(): List {
+ val errors = mutableListOf()
+ if (cryptoServiceName == null && cryptoServiceConf != null) {
+ errors += "cryptoServiceName is null, but cryptoServiceConf is set to $cryptoServiceConf"
+ }
+ return errors
+ }
+
override fun validate(): List {
val errors = mutableListOf()
errors += validateDevModeOptions()
@@ -309,6 +336,7 @@ data class NodeConfigurationImpl(
errors += validateTlsCertCrlConfig()
errors += validateNetworkServices()
errors += validateH2Settings()
+ errors += validateCryptoService()
return errors
}
diff --git a/node/src/main/kotlin/net/corda/node/services/keys/BasicHSMKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/BasicHSMKeyManagementService.kt
new file mode 100644
index 0000000000..17df59ddb3
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/services/keys/BasicHSMKeyManagementService.kt
@@ -0,0 +1,155 @@
+package net.corda.node.services.keys
+
+import net.corda.core.crypto.*
+import net.corda.core.identity.PartyAndCertificate
+import net.corda.core.internal.NamedCacheFactory
+import net.corda.core.serialization.SingletonSerializeAsToken
+import net.corda.core.serialization.serialize
+import net.corda.core.utilities.MAX_HASH_HEX_SIZE
+import net.corda.node.services.identity.PersistentIdentityService
+import net.corda.core.crypto.internal.AliasPrivateKey
+import net.corda.node.utilities.AppendOnlyPersistentMap
+import net.corda.nodeapi.internal.cryptoservice.CryptoService
+import net.corda.nodeapi.internal.persistence.CordaPersistence
+import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
+import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
+import org.bouncycastle.operator.ContentSigner
+import java.security.KeyPair
+import java.security.PrivateKey
+import java.security.PublicKey
+import javax.persistence.Column
+import javax.persistence.Entity
+import javax.persistence.Id
+import javax.persistence.Lob
+
+/**
+ * A persistent re-implementation of [E2ETestKeyManagementService] to support CryptoService for initial keys and
+ * database storage for anonymous fresh keys.
+ *
+ * This is not the long-term implementation. See the list of items in the above class.
+ *
+ * This class needs database transactions to be in-flight during method calls and init.
+ */
+class BasicHSMKeyManagementService(cacheFactory: NamedCacheFactory, val identityService: PersistentIdentityService,
+ private val database: CordaPersistence, private val cryptoService: CryptoService) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
+ @Entity
+ @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
+ class PersistentKey(
+
+ @Id
+ @Column(name = "public_key_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
+ var publicKeyHash: String,
+
+ @Lob
+ @Column(name = "public_key", nullable = false)
+ var publicKey: ByteArray = EMPTY_BYTE_ARRAY,
+ @Lob
+ @Column(name = "private_key", nullable = false)
+ var privateKey: ByteArray = EMPTY_BYTE_ARRAY
+ ) {
+ constructor(publicKey: PublicKey, privateKey: PrivateKey)
+ : this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded)
+ }
+
+ private companion object {
+ fun createKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap {
+ return AppendOnlyPersistentMap(
+ cacheFactory = cacheFactory,
+ name = "BasicHSMKeyManagementService_keys",
+ toPersistentEntityKey = { it.toStringShort() },
+ fromPersistentEntity = { Pair(Crypto.decodePublicKey(it.publicKey), Crypto.decodePrivateKey(
+ it.privateKey)) },
+ toPersistentEntity = { key: PublicKey, value: PrivateKey ->
+ PersistentKey(key, value)
+ },
+ persistentEntityClass = PersistentKey::class.java
+ )
+ }
+ }
+
+ // Maintain a map from PublicKey to alias for the initial keys.
+ private val originalKeysMap = mutableMapOf()
+ // A map for anonymous keys.
+ private val keysMap = createKeyMap(cacheFactory)
+
+ override fun start(initialKeyPairs: Set) {
+ initialKeyPairs.forEach {
+ require(it.private is AliasPrivateKey) { "${this.javaClass.name} supports AliasPrivateKeys only, but ${it.private.algorithm} key was found" }
+ originalKeysMap[Crypto.toSupportedPublicKey(it.public)] = (it.private as AliasPrivateKey).alias
+ }
+ }
+
+ override val keys: Set get() = database.transaction { originalKeysMap.keys.plus(keysMap.allPersisted().map { it.first }.toSet()) }
+
+ private fun containsPublicKey(publicKey: PublicKey): Boolean {
+ return (publicKey in originalKeysMap || publicKey in keysMap)
+ }
+
+ override fun filterMyKeys(candidateKeys: Iterable): Iterable = database.transaction {
+ identityService.stripCachedPeerKeys(candidateKeys).filter { containsPublicKey(it) } // TODO: bulk cache access.
+ }
+
+ // Unlike initial keys, freshkey() is related confidential keys and it utilises platform's software key generation
+ // thus, without using [cryptoService]).
+ override fun freshKey(): PublicKey {
+ val keyPair = generateKeyPair()
+ database.transaction {
+ keysMap[keyPair.public] = keyPair.private
+ }
+ return keyPair.public
+ }
+
+ override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
+ return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey))
+ }
+
+ private fun getSigner(publicKey: PublicKey): ContentSigner {
+ val signingPublicKey = getSigningPublicKey(publicKey)
+ return if (signingPublicKey in originalKeysMap) {
+ cryptoService.getSigner(originalKeysMap[signingPublicKey]!!)
+ } else {
+ getSigner(getSigningKeyPair(signingPublicKey))
+ }
+ }
+
+ // Get [KeyPair] for the input [publicKey]. This is used for fresh keys, in which we have access to the private key material.
+ private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
+ return database.transaction {
+ KeyPair(publicKey, keysMap[publicKey]!!)
+ }
+ }
+
+ // It looks for the PublicKey in the (potentially) CompositeKey that is ours.
+ // TODO what if we own two or more leaves of a CompositeKey?
+ private fun getSigningPublicKey(publicKey: PublicKey): PublicKey {
+ return publicKey.keys.first { containsPublicKey(it) }
+ }
+
+ override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
+ val signingPublicKey = getSigningPublicKey(publicKey)
+ return if (signingPublicKey in originalKeysMap) {
+ DigitalSignature.WithKey(signingPublicKey, cryptoService.sign(originalKeysMap[signingPublicKey]!!, bytes))
+ } else {
+ val keyPair = getSigningKeyPair(signingPublicKey)
+ keyPair.sign(bytes)
+ }
+ }
+
+ // TODO: A full KeyManagementService implementation needs to record activity to the Audit Service and to limit
+ // signing to appropriately authorised contexts and initiating users.
+ override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
+ val signingPublicKey = getSigningPublicKey(publicKey)
+ return if (signingPublicKey in originalKeysMap) {
+ val sigKey: SignatureScheme = Crypto.findSignatureScheme(signingPublicKey)
+ val sigMetaData: SignatureScheme = Crypto.findSignatureScheme(signableData.signatureMetadata.schemeNumberID)
+ require(sigKey == sigMetaData || sigMetaData == Crypto.COMPOSITE_KEY) {
+ "Metadata schemeCodeName: ${sigMetaData.schemeCodeName} is not aligned with the key type: ${sigKey.schemeCodeName}."
+ }
+ val signatureBytes = cryptoService.sign(originalKeysMap[signingPublicKey]!!, signableData.serialize().bytes)
+ TransactionSignature(signatureBytes, signingPublicKey, signableData.signatureMetadata)
+ } else {
+ val keyPair = getSigningKeyPair(signingPublicKey)
+ keyPair.sign(signableData)
+ }
+ }
+}
diff --git a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt
index c99732a356..c33d28e642 100644
--- a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt
@@ -5,6 +5,9 @@ import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SingletonSerializeAsToken
+import net.corda.core.crypto.internal.AliasPrivateKey
+import net.corda.node.services.keys.cryptoservice.BCCryptoService
+import net.corda.nodeapi.internal.cryptoservice.CryptoService
import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.PrivateKey
@@ -24,7 +27,7 @@ import javax.annotation.concurrent.ThreadSafe
* etc.
*/
@ThreadSafe
-class E2ETestKeyManagementService(val identityService: IdentityService) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
+class E2ETestKeyManagementService(val identityService: IdentityService, private val cryptoService: CryptoService? = null) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
private class InnerState {
val keys = HashMap()
}
@@ -32,12 +35,20 @@ class E2ETestKeyManagementService(val identityService: IdentityService) : Single
private val mutex = ThreadBox(InnerState())
// Accessing this map clones it.
override val keys: Set get() = mutex.locked { keys.keys }
+ // Maintain a map from PublicKey to alias for the initial keys.
+
val keyPairs: Set get() = mutex.locked { keys.map { KeyPair(it.key, it.value) }.toSet() }
override fun start(initialKeyPairs: Set) {
mutex.locked {
for (key in initialKeyPairs) {
- keys[key.public] = key.private
+ var privateKey = key.private
+ if (privateKey is AliasPrivateKey && cryptoService is BCCryptoService) {
+ privateKey = cryptoService.certificateStore.query {
+ getPrivateKey((privateKey as AliasPrivateKey).alias, cryptoService.certificateStore.entryPassword)
+ }
+ }
+ keys[key.public] = privateKey
}
}
}
diff --git a/node/src/main/kotlin/net/corda/node/services/keys/cryptoservice/BCCryptoService.kt b/node/src/main/kotlin/net/corda/node/services/keys/cryptoservice/BCCryptoService.kt
new file mode 100644
index 0000000000..744b6a15d1
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/services/keys/cryptoservice/BCCryptoService.kt
@@ -0,0 +1,66 @@
+package net.corda.node.services.keys.cryptoservice
+
+import net.corda.core.crypto.Crypto
+import net.corda.core.crypto.newSecureRandom
+import net.corda.node.services.config.NodeConfiguration
+import net.corda.nodeapi.internal.config.CertificateStore
+import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
+import net.corda.nodeapi.internal.crypto.X509Utilities
+import net.corda.nodeapi.internal.cryptoservice.CryptoService
+import org.bouncycastle.operator.ContentSigner
+import java.security.KeyPair
+import java.security.KeyStore
+import java.security.PublicKey
+
+/**
+ * Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
+ * and a Java KeyStore in the form of [CertificateStore] to store private keys.
+ * This service reuses the [NodeConfiguration.signingCertificateStore] to store keys.
+ */
+class BCCryptoService(private val nodeConf: NodeConfiguration) : CryptoService {
+
+ // TODO check if keyStore exists.
+ // TODO make it private when E2ETestKeyManagementService does not require direct access to the private key.
+ internal var certificateStore: CertificateStore = nodeConf.signingCertificateStore.get(true)
+
+ override fun generateKeyPair(alias: String, schemeNumberID: Int): PublicKey {
+ val keyPair = Crypto.generateKeyPair(Crypto.findSignatureScheme(schemeNumberID))
+ importKey(alias, keyPair)
+ return keyPair.public
+ }
+
+ override fun containsKey(alias: String): Boolean {
+ return certificateStore.contains(alias)
+ }
+
+ override fun getPublicKey(alias: String): PublicKey {
+ return certificateStore.query { getPublicKey(alias) }
+ }
+
+ override fun sign(alias: String, data: ByteArray): ByteArray {
+ return Crypto.doSign(certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) } , data)
+ }
+
+ override fun getSigner(alias: String): ContentSigner {
+ val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
+ val signatureScheme = Crypto.findSignatureScheme(privateKey)
+ return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
+ }
+
+ /**
+ * If a node is running in [NodeConfiguration.devMode] and for backwards compatibility purposes, the same [KeyStore]
+ * is reused outside [BCCryptoService] to update certificate paths. [resyncKeystore] will sync [BCCryptoService]'s
+ * loaded [certificateStore] in memory with the contents of the corresponding [KeyStore] file.
+ */
+ fun resyncKeystore() {
+ certificateStore = nodeConf.signingCertificateStore.get(true)
+ }
+
+ /** Import an already existing [KeyPair] to this [CryptoService]. */
+ fun importKey(alias: String, keyPair: KeyPair) {
+ // Store a self-signed certificate, as Keystore requires to store certificates instead of public keys.
+ // We could probably add a null cert, but we store a self-signed cert that will be used to retrieve the public key.
+ val cert = X509Utilities.createSelfSignedCACertificate(nodeConf.myLegalName.x500Principal, keyPair)
+ certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
+ }
+}
diff --git a/node/src/main/kotlin/net/corda/node/services/keys/cryptoservice/SupportedCryptoServices.kt b/node/src/main/kotlin/net/corda/node/services/keys/cryptoservice/SupportedCryptoServices.kt
new file mode 100644
index 0000000000..b76b25cdc1
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/services/keys/cryptoservice/SupportedCryptoServices.kt
@@ -0,0 +1,9 @@
+package net.corda.node.services.keys.cryptoservice
+
+enum class SupportedCryptoServices {
+ /** Identifier for [BCCryptoService]. */
+ BC_SIMPLE
+ // UTIMACO, // Utimaco HSM.
+ // GEMALTO_LUNA, // Gemalto Luna HSM.
+ // AZURE_KV // Azure key Vault.
+}
diff --git a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt
index 842af5589c..344ab468c1 100644
--- a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt
@@ -12,6 +12,7 @@ import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.SchemaService.SchemaOptions
import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.identity.PersistentIdentityService
+import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.P2PMessageDeduplicator
import net.corda.node.services.persistence.DBCheckpointStorage
@@ -35,6 +36,7 @@ class NodeSchemaService(private val extraSchemas: Set = emptySet()
object NodeCoreV1 : MappedSchema(schemaFamily = NodeCore.javaClass, version = 1,
mappedTypes = listOf(DBCheckpointStorage.DBCheckpoint::class.java,
DBTransactionStorage.DBTransaction::class.java,
+ BasicHSMKeyManagementService.PersistentKey::class.java,
PersistentKeyManagementService.PersistentKey::class.java,
NodeSchedulerService.PersistentScheduledState::class.java,
NodeAttachmentService.DBAttachment::class.java,
diff --git a/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt
index 5c2b9a1241..11c01ccfd1 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt
+++ b/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt
@@ -56,6 +56,7 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
name == "DeduplicationChecker_watermark" -> caffeine
name == "BFTNonValidatingNotaryService_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "RaftUniquenessProvider_transactions" -> caffeine.maximumSize(defaultCacheSize)
+ name == "BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)
else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?")
}
}
diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt
index e8f916dbb7..06b429b194 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt
+++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt
@@ -1,28 +1,31 @@
package net.corda.node.utilities.registration
+import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.utilities.contextLogger
import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration
+import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.nodeapi.internal.config.CertificateStore
-import net.corda.nodeapi.internal.config.CertificateStoreSupplier
import net.corda.nodeapi.internal.crypto.CertificateType
+import net.corda.nodeapi.internal.crypto.NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
+import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
+import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.util.io.pem.PemObject
import java.io.IOException
import java.io.StringWriter
import java.net.ConnectException
import java.nio.file.Path
import java.security.KeyPair
-import java.security.KeyStore
import java.security.PublicKey
import java.security.cert.X509Certificate
import java.time.Duration
@@ -33,23 +36,25 @@ import javax.security.auth.x500.X500Principal
* Helper for managing the node registration process, which checks for any existing certificates and requests them if
* needed.
*/
-// TODO: Use content signer instead of keypairs.
-open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
- private val signingCertificateStore: CertificateStoreSupplier,
- private val myLegalName: CordaX500Name,
- private val emailAddress: String,
- private val certService: NetworkRegistrationService,
- private val networkRootTrustStorePath: Path,
- networkRootTrustStorePassword: String,
- private val keyAlias: String,
- private val certRole: CertRole,
- private val nextIdleDuration: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) {
+open class NetworkRegistrationHelper(
+ config: NodeConfiguration,
+ private val certService: NetworkRegistrationService,
+ private val networkRootTrustStorePath: Path,
+ networkRootTrustStorePassword: String,
+ private val nodeCaKeyAlias: String,
+ private val certRole: CertRole,
+ private val nextIdleDuration: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))
+) {
companion object {
const val SELF_SIGNED_PRIVATE_KEY = "SelfSignedPrivateKey"
val logger = contextLogger()
}
-
+ private val certificatesDirectory: Path = config.certificatesDirectory
+ private val myLegalName: CordaX500Name = config.myLegalName
+ private val emailAddress: String = config.emailAddress
+ private val cryptoService = config.makeCryptoService()
+ private val certificateStore = config.signingCertificateStore.get(true)
private val requestIdStore = certificatesDirectory / "certificate-request-id.txt"
protected val rootTrustStore: X509KeyStore
protected val rootCert: X509Certificate
@@ -64,57 +69,67 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
}
/**
- * Ensure the initial keystore for a node is set up.
+ * Ensure the initial keys and certificates for a node are set up.
*
* This checks the "config.certificatesDirectory" field for certificates required to connect to a Corda network.
* If the certificates are not found, a PKCS #10 certification request will be submitted to the
* Corda network permissioning server via [NetworkRegistrationService]. This process will enter a polling loop until
- * the request has been approved, and then the certificate chain will be downloaded and stored in [KeyStore] reside in
- * the certificates directory.
+ * the request has been approved, and then the certificate chain will be downloaded and stored in [certificateStore].
*
* @throws CertificateRequestException if the certificate retrieved by doorman is invalid.
*/
- fun buildKeystore() {
+ fun generateKeysAndRegister() {
certificatesDirectory.createDirectories()
- val nodeKeyStore = signingCertificateStore.get(createNew = true)
- if (keyAlias in nodeKeyStore) {
+ // We need this in case cryptoService and certificateStore share the same KeyStore (for backwards compatibility purposes).
+ // If we didn't, then an update to cryptoService wouldn't be reflected to certificateStore that is already loaded in memory.
+ val certStore: CertificateStore = if (cryptoService is BCCryptoService) cryptoService.certificateStore else certificateStore
+
+ // SELF_SIGNED_PRIVATE_KEY is used as progress indicator.
+ if (certStore.contains(nodeCaKeyAlias) && !certStore.contains(SELF_SIGNED_PRIVATE_KEY)) {
println("Certificate already exists, Corda node will now terminate...")
return
}
- // TODO: Use different password for private key.
- val privateKeyPassword = nodeKeyStore.password
+
+ val tlsCrlIssuerCert = getTlsCrlIssuerCert()
+
+ // We use SELF_SIGNED_PRIVATE_KEY as progress indicator so we just store a dummy key and cert.
+ // When registration succeeds, this entry should be deleted.
+ certStore.query { setPrivateKey(SELF_SIGNED_PRIVATE_KEY, AliasPrivateKey(SELF_SIGNED_PRIVATE_KEY), listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), certificateStore.entryPassword) }
+
+ val nodeCaPublicKey = loadOrGenerateKeyPair()
+
+ val requestId = submitOrResumeCertificateSigningRequest(nodeCaPublicKey, cryptoService.getSigner(nodeCaKeyAlias))
+
+ val nodeCaCertificates = pollServerForCertificates(requestId)
+ validateCertificates(nodeCaPublicKey, nodeCaCertificates)
+
+ certStore.setCertPathOnly(nodeCaKeyAlias, nodeCaCertificates)
+ certStore.value.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
+ certStore.value.save()
+ println("Private key '$nodeCaKeyAlias' and its certificate-chain stored successfully.")
+
+ onSuccess(nodeCaPublicKey, cryptoService.getSigner(nodeCaKeyAlias), nodeCaCertificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name())
+ // All done, clean up temp files.
+ requestIdStore.deleteIfExists()
+ }
+
+ private fun loadOrGenerateKeyPair(): PublicKey {
+ return if (cryptoService.containsKey(nodeCaKeyAlias)) {
+ cryptoService.getPublicKey(nodeCaKeyAlias)!!
+ } else {
+ cryptoService.generateKeyPair(nodeCaKeyAlias, X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME.schemeNumberID)
+ }
+ }
+
+ private fun getTlsCrlIssuerCert(): X509Certificate? {
val tlsCrlIssuerCert = validateAndGetTlsCrlIssuerCert()
if (tlsCrlIssuerCert == null && isTlsCrlIssuerCertRequired()) {
System.err.println("""tlsCrlIssuerCert config does not match the root certificate issuer and nor is there any other certificate in the trust store with a matching issuer.
- | Please make sure the config is correct or that the correct certificate for the CRL issuer is added to the node's trust store.
- | The node will now terminate.""".trimMargin())
+ | Please make sure the config is correct or that the correct certificate for the CRL issuer is added to the node's trust store.
+ | The node will now terminate.""".trimMargin())
throw IllegalArgumentException("TLS CRL issuer certificate not found in the trust store.")
}
-
- val keyPair = nodeKeyStore.loadOrCreateKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
-
- val requestId = try {
- submitOrResumeCertificateSigningRequest(keyPair)
- } catch (e: Exception) {
- throw if (e is ConnectException || e is ServiceUnavailableException || e is IOException) {
- NodeRegistrationException(e.message, e)
- } else e
- }
-
- val certificates = try {
- pollServerForCertificates(requestId)
- } catch (certificateRequestException: CertificateRequestException) {
- System.err.println(certificateRequestException.message)
- System.err.println("Please make sure the details in configuration file are correct and try again.")
- System.err.println("Corda node will now terminate.")
- requestIdStore.deleteIfExists()
- throw certificateRequestException
- }
- validateCertificates(keyPair.public, certificates)
- storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias, privateKeyPassword)
- onSuccess(keyPair, certificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name())
- // All done, clean up temp files.
- requestIdStore.deleteIfExists()
+ return tlsCrlIssuerCert
}
private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List) {
@@ -149,18 +164,6 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
println("Certificate signing request approved, storing private key with the certificate chain.")
}
- private fun storePrivateKeyWithCertificates(nodeKeystore: CertificateStore, keyPair: KeyPair, certificates: List, keyAlias: String, keyPassword: String) {
- // Save private key and certificate chain to the key store.
- with(nodeKeystore.value) {
- setPrivateKey(keyAlias, keyPair.private, certificates, keyPassword = keyPassword)
- // The key was temporarily stored as SELF_SIGNED_PRIVATE_KEY, but now that it's signed by the Doorman we
- // can delete this old record.
- internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
- save()
- }
- println("Private key '$keyAlias' and certificate stored in node signing keystore.")
- }
-
private fun CertificateStore.loadOrCreateKeyPair(alias: String, entryPassword: String = password): KeyPair {
// 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.
@@ -184,65 +187,80 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
* @return List of certificate chain.
*/
private fun pollServerForCertificates(requestId: String): List {
- println("Start polling server for certificate signing approval.")
- // Poll server to download the signed certificate once request has been approved.
- var idlePeriodDuration: Duration? = null
- while (true) {
- try {
- val (pollInterval, certificates) = certService.retrieveCertificates(requestId)
- if (certificates != null) {
- return certificates
- }
- Thread.sleep(pollInterval.toMillis())
- } catch (e: ServiceUnavailableException) {
- idlePeriodDuration = nextIdleDuration(idlePeriodDuration)
- if (idlePeriodDuration != null) {
- Thread.sleep(idlePeriodDuration.toMillis())
- } else {
- throw NodeRegistrationException("Compatibility Zone registration service is currently unavailable, "
- + "try again later!.", e)
+ try {
+ println("Start polling server for certificate signing approval.")
+ // Poll server to download the signed certificate once request has been approved.
+ var idlePeriodDuration: Duration? = null
+ while (true) {
+ try {
+ val (pollInterval, certificates) = certService.retrieveCertificates(requestId)
+ if (certificates != null) {
+ return certificates
+ }
+ Thread.sleep(pollInterval.toMillis())
+ } catch (e: ServiceUnavailableException) {
+ idlePeriodDuration = nextIdleDuration(idlePeriodDuration)
+ if (idlePeriodDuration != null) {
+ Thread.sleep(idlePeriodDuration.toMillis())
+ } else {
+ throw NodeRegistrationException("Compatibility Zone registration service is currently unavailable, "
+ + "try again later!.", e)
+ }
}
}
+ } catch (certificateRequestException: CertificateRequestException) {
+ System.err.println(certificateRequestException.message)
+ System.err.println("Please make sure the details in configuration file are correct and try again.")
+ System.err.println("Corda node will now terminate.")
+ requestIdStore.deleteIfExists()
+ throw certificateRequestException
}
}
/**
- * Submit Certificate Signing Request to Certificate signing service if request ID not found in file system
+ * Submit Certificate Signing Request to Certificate signing service if request ID not found in file system.
* New request ID will be stored in requestId.txt
- * @param keyPair Public Private key pair generated for SSL certification.
+ * @param publicKey public key for which we need a certificate.
+ * @param contentSigner the [ContentSigner] that will sign the CSR.
* @return Request ID return from the server.
*/
- private fun submitOrResumeCertificateSigningRequest(keyPair: KeyPair): String {
- // Retrieve request id from file if exists, else post a request to server.
- return if (!requestIdStore.exists()) {
- val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, keyPair, certRole)
- val writer = StringWriter()
- JcaPEMWriter(writer).use {
- it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded))
+ private fun submitOrResumeCertificateSigningRequest(publicKey: PublicKey, contentSigner: ContentSigner): String {
+ try {
+ // Retrieve request id from file if exists, else post a request to server.
+ return if (!requestIdStore.exists()) {
+ val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, publicKey, contentSigner, certRole)
+ val writer = StringWriter()
+ JcaPEMWriter(writer).use {
+ it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded))
+ }
+ println("Certificate signing request with the following information will be submitted to the Corda certificate signing server.")
+ println()
+ println("Legal Name: $myLegalName")
+ println("Email: $emailAddress")
+ println()
+ println("Public Key: $publicKey")
+ println()
+ println("$writer")
+ // Post request to signing server via http.
+ println("Submitting certificate signing request to Corda certificate signing server.")
+ val requestId = certService.submitRequest(request)
+ // Persists request ID to file in case of node shutdown.
+ requestIdStore.writeLines(listOf(requestId))
+ println("Successfully submitted request to Corda certificate signing server, request ID: $requestId.")
+ requestId
+ } else {
+ val requestId = requestIdStore.readLines { it.findFirst().get() }
+ println("Resuming from previous certificate signing request, request ID: $requestId.")
+ requestId
}
- println("Certificate signing request with the following information will be submitted to the Corda certificate signing server.")
- println()
- println("Legal Name: $myLegalName")
- println("Email: $emailAddress")
- println()
- println("Public Key: ${keyPair.public}")
- println()
- println("$writer")
- // Post request to signing server via http.
- println("Submitting certificate signing request to Corda certificate signing server.")
- val requestId = certService.submitRequest(request)
- // Persists request ID to file in case of node shutdown.
- requestIdStore.writeLines(listOf(requestId))
- println("Successfully submitted request to Corda certificate signing server, request ID: $requestId.")
- requestId
- } else {
- val requestId = requestIdStore.readLines { it.findFirst().get() }
- println("Resuming from previous certificate signing request, request ID: $requestId.")
- requestId
+ } catch (e: Exception) {
+ throw if (e is ConnectException || e is ServiceUnavailableException || e is IOException) {
+ NodeRegistrationException(e.message, e)
+ } else e
}
}
- protected open fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List, tlsCrlCertificateIssuer: X500Name?) {}
+ protected open fun onSuccess(publicKey: PublicKey, contentSigner: ContentSigner, certificates: List, tlsCrlCertificateIssuer: X500Name?) {}
protected open fun validateAndGetTlsCrlIssuerCert(): X509Certificate? = null
@@ -256,14 +274,9 @@ class NodeRegistrationException(
class NodeRegistrationHelper(
private val config: NodeConfiguration,
- certService: NetworkRegistrationService,
- regConfig: NodeRegistrationOption,
- computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))
-) : NetworkRegistrationHelper(
- config.certificatesDirectory,
- config.signingCertificateStore,
- config.myLegalName,
- config.emailAddress,
+ certService: NetworkRegistrationService, regConfig: NodeRegistrationOption, computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) :
+ NetworkRegistrationHelper(
+ config,
certService,
regConfig.networkRootTrustStorePath,
regConfig.networkRootTrustStorePassword,
@@ -275,29 +288,38 @@ class NodeRegistrationHelper(
val logger = contextLogger()
}
- override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List, tlsCrlCertificateIssuer: X500Name?) {
- createSSLKeystore(nodeCAKeyPair, certificates, tlsCrlCertificateIssuer)
+ override fun onSuccess(publicKey: PublicKey, contentSigner: ContentSigner, certificates: List, tlsCrlCertificateIssuer: X500Name?) {
+ createSSLKeystore(publicKey, contentSigner, certificates, tlsCrlCertificateIssuer)
createTruststore(certificates.last())
}
- private fun createSSLKeystore(nodeCAKeyPair: KeyPair, certificates: List, tlsCertCrlIssuer: X500Name?) {
+ private fun createSSLKeystore(nodeCaPublicKey: PublicKey, nodeCaContentSigner: ContentSigner, nodeCaCertificateChain: List, tlsCertCrlIssuer: X500Name?) {
val keyStore = config.p2pSslOptions.keyStore
val certificateStore = keyStore.get(createNew = true)
certificateStore.update {
println("Generating SSL certificate for node messaging service.")
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
+ val issuerCertificate = nodeCaCertificateChain.first()
+ val validityWindow = X509Utilities.getCertificateValidityWindow(DEFAULT_VALIDITY_WINDOW.first, DEFAULT_VALIDITY_WINDOW.second, issuerCertificate)
+
val sslCert = X509Utilities.createCertificate(
CertificateType.TLS,
- certificates.first(),
- nodeCAKeyPair,
+ issuerCertificate.subjectX500Principal,
+ nodeCaPublicKey,
+ nodeCaContentSigner,
config.myLegalName.x500Principal,
sslKeyPair.public,
+ validityWindow,
crlDistPoint = config.tlsCertCrlDistPoint?.toString(),
crlIssuer = tlsCertCrlIssuer)
+
logger.info("Generated TLS certificate: $sslCert")
- setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates, certificateStore.entryPassword)
+
+ val sslCertificateChain: List = listOf(sslCert) + nodeCaCertificateChain
+ X509Utilities.validateCertificateChain(rootCert, sslCertificateChain)
+ setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, sslCertificateChain, keyStore.entryPassword)
}
- println("SSL private key and certificate stored in ${keyStore.path}.")
+ println("SSL private key and certificate chain stored in ${keyStore.path}.")
}
private fun createTruststore(rootCertificate: X509Certificate) {
@@ -365,4 +387,4 @@ private class FixedPeriodLimitedRetrialStrategy(times: Int, private val period:
return if (counter-- > 0) period else null
}
}
-}
\ No newline at end of file
+}
diff --git a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt
index 3544ee5e85..09faa02886 100644
--- a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt
+++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt
@@ -76,19 +76,6 @@ class NetworkParametersTest {
}
}
- @Test
- fun `maxTransactionSize must be bigger than maxMesssageSize`() {
- assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
- NetworkParameters(1,
- emptyList(),
- 2000,
- 2001,
- Instant.now(),
- 1,
- emptyMap())
- }.withMessage("maxTransactionSize cannot be bigger than maxMessageSize")
- }
-
@Test
fun `package ownership checks are correct`() {
val key1 = generateKeyPair().public
diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
index ac22fe3696..aef48b5558 100644
--- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
@@ -139,12 +139,11 @@ class NodeConfigurationImplTest {
private fun getConfig(cfgName: String, overrides: Config = ConfigFactory.empty()): Config {
val path = this::class.java.classLoader.getResource(cfgName).toPath()
- val cfg = ConfigHelper.loadConfig(
+ return ConfigHelper.loadConfig(
baseDirectory = path.parent,
configFile = path,
configOverrides = overrides
)
- return cfg
}
@Test
@@ -214,6 +213,24 @@ class NodeConfigurationImplTest {
}
}
+ @Test
+ fun `validation has error on non-null cryptoServiceConf for null cryptoServiceName`() {
+ val configuration = testConfiguration.copy(cryptoServiceConf = "unsupported.conf")
+
+ val errors = configuration.validate()
+
+ assertThat(errors).hasOnlyOneElementSatisfying {
+ error -> error.contains("cryptoServiceName is null, but cryptoServiceConf is set to unsupported.conf")
+ }
+ }
+
+ @Test
+ fun `fail on wrong cryptoServiceName`() {
+ var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
+ rawConfig = rawConfig.withValue("cryptoServiceName", ConfigValueFactory.fromAnyRef("UNSUPPORTED"))
+ assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.hasMessageStartingWith("UNSUPPORTED is not one of")
+ }
+
@Test
fun `rpcAddress and rpcSettings_address are equivalent`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
@@ -247,7 +264,7 @@ class NodeConfigurationImplTest {
@Test
fun `jmxReporterType is null and defaults to Jokolia`() {
- var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
+ val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
val nodeConfig = rawConfig.parseAsNodeConfiguration()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
}
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt
index 9e12b9b60e..292fec8a2d 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt
@@ -35,6 +35,7 @@ import net.corda.testing.node.internal.startFlow
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.junit.*
import java.io.ByteArrayOutputStream
+import java.io.Closeable
import java.io.OutputStream
import java.net.URI
import java.nio.charset.StandardCharsets
@@ -80,25 +81,30 @@ class NodeAttachmentServiceTest {
@After
fun tearDown() {
- dir.list { subdir ->
- subdir.forEach(Path::deleteRecursively)
- }
database.close()
}
@Test
fun `importing a signed jar saves the signers to the storage`() {
- val jarAndSigner = makeTestSignedContractJar("com.example.MyContract")
- val signedJar = jarAndSigner.first
- val attachmentId = storage.importAttachment(signedJar.inputStream(), "test", null)
- assertEquals(listOf(jarAndSigner.second.hash), storage.openAttachment(attachmentId)!!.signers.map { it.hash })
+ SelfCleaningDir().use { file ->
+ val jarAndSigner = makeTestSignedContractJar(file.path, "com.example.MyContract")
+ val signedJar = jarAndSigner.first
+ signedJar.inputStream().use { jarStream ->
+ val attachmentId = storage.importAttachment(jarStream, "test", null)
+ assertEquals(listOf(jarAndSigner.second.hash), storage.openAttachment(attachmentId)!!.signers.map { it.hash })
+ }
+ }
}
@Test
fun `importing a non-signed jar will save no signers`() {
- val jarName = makeTestContractJar("com.example.MyContract")
- val attachmentId = storage.importAttachment(dir.resolve(jarName).inputStream(), "test", null)
- assertEquals(0, storage.openAttachment(attachmentId)!!.signers.size)
+ SelfCleaningDir().use {
+ val jarName = makeTestContractJar(it.path, "com.example.MyContract")
+ it.path.resolve(jarName).inputStream().use { jarStream ->
+ val attachmentId = storage.importAttachment(jarStream, "test", null)
+ assertEquals(0, storage.openAttachment(attachmentId)!!.signers.size)
+ }
+ }
}
@Test
@@ -127,25 +133,27 @@ class NodeAttachmentServiceTest {
@Test
fun `insert contract attachment as an untrusted uploader and then as trusted CorDapp uploader`() {
- val contractJarName = makeTestContractJar("com.example.MyContract")
- val testJar = dir.resolve(contractJarName)
- val expectedHash = testJar.readAll().sha256()
+ SelfCleaningDir().use { file ->
+ val contractJarName = makeTestContractJar(file.path, "com.example.MyContract")
+ val testJar = file.path.resolve(contractJarName)
+ val expectedHash = testJar.readAll().sha256()
- // PRIVILEGED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, P2P_UPLOADER, UNKNOWN_UPLOADER)
- // TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER)
+ // PRIVILEGED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, P2P_UPLOADER, UNKNOWN_UPLOADER)
+ // TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER)
- database.transaction {
- val id = testJar.read { storage.privilegedImportOrGetAttachment(it, P2P_UPLOADER, null) }
- assertEquals(expectedHash, id)
- val attachment1 = storage.openAttachment(expectedHash)
+ database.transaction {
+ val id = testJar.read { storage.privilegedImportOrGetAttachment(it, P2P_UPLOADER, null) }
+ assertEquals(expectedHash, id)
+ val attachment1 = storage.openAttachment(expectedHash)
- val id2 = testJar.read { storage.privilegedImportOrGetAttachment(it, DEPLOYED_CORDAPP_UPLOADER, null) }
- assertEquals(expectedHash, id2)
- val attachment2 = storage.openAttachment(expectedHash)
+ val id2 = testJar.read { storage.privilegedImportOrGetAttachment(it, DEPLOYED_CORDAPP_UPLOADER, null) }
+ assertEquals(expectedHash, id2)
+ val attachment2 = storage.openAttachment(expectedHash)
- assertNotEquals(attachment1, attachment2)
- assertEquals(P2P_UPLOADER, (attachment1 as ContractAttachment).uploader)
- assertEquals(DEPLOYED_CORDAPP_UPLOADER, (attachment2 as ContractAttachment).uploader)
+ assertNotEquals(attachment1, attachment2)
+ assertEquals(P2P_UPLOADER, (attachment1 as ContractAttachment).uploader)
+ assertEquals(DEPLOYED_CORDAPP_UPLOADER, (attachment2 as ContractAttachment).uploader)
+ }
}
}
@@ -350,20 +358,17 @@ class NodeAttachmentServiceTest {
return Pair(file, file.readAll().sha256())
}
+ /**
+ * Class to create an automatically delete a temporary directory.
+ */
+ class SelfCleaningDir : Closeable {
+ val path: Path = Files.createTempDirectory(NodeAttachmentServiceTest::class.simpleName)
+ override fun close() {
+ path.deleteRecursively()
+ }
+ }
+
companion object {
- private val dir = Files.createTempDirectory(NodeAttachmentServiceTest::class.simpleName)
-
- @BeforeClass
- @JvmStatic
- fun beforeClass() {
- }
-
- @AfterClass
- @JvmStatic
- fun afterClass() {
- dir.deleteRecursively()
- }
-
private fun makeTestJar(output: OutputStream, extraEntries: List> = emptyList()) {
output.use {
val jar = JarOutputStream(it)
@@ -372,33 +377,33 @@ class NodeAttachmentServiceTest {
jar.closeEntry()
jar.putNextEntry(JarEntry("test2.txt"))
jar.write("Some more useful content".toByteArray())
- extraEntries.forEach {
- jar.putNextEntry(JarEntry(it.first))
- jar.write(it.second.toByteArray())
+ extraEntries.forEach { entry ->
+ jar.putNextEntry(JarEntry(entry.first))
+ jar.write(entry.second.toByteArray())
}
jar.closeEntry()
}
}
- private fun makeTestSignedContractJar(contractName: String): Pair {
+ private fun makeTestSignedContractJar(workingDir: Path, contractName: String): Pair {
val alias = "testAlias"
val pwd = "testPassword"
- dir.generateKey(alias, pwd, ALICE_NAME.toString())
- val jarName = makeTestContractJar(contractName)
- val signer = dir.signJar(jarName, alias, pwd)
- return dir.resolve(jarName) to signer
+ workingDir.generateKey(alias, pwd, ALICE_NAME.toString())
+ val jarName = makeTestContractJar(workingDir, contractName)
+ val signer = workingDir.signJar(jarName, alias, pwd)
+ return workingDir.resolve(jarName) to signer
}
- private fun makeTestContractJar(contractName: String): String {
+ private fun makeTestContractJar(workingDir: Path, contractName: String): String {
val packages = contractName.split(".")
val jarName = "testattachment.jar"
val className = packages.last()
- createTestClass(className, packages.subList(0, packages.size - 1))
- dir.createJar(jarName, "${contractName.replace(".", "/")}.class")
+ createTestClass(workingDir, className, packages.subList(0, packages.size - 1))
+ workingDir.createJar(jarName, "${contractName.replace(".", "/")}.class")
return jarName
}
- private fun createTestClass(className: String, packages: List): Path {
+ private fun createTestClass(workingDir: Path, className: String, packages: List): Path {
val newClass = """package ${packages.joinToString(".")};
import net.corda.core.contracts.*;
import net.corda.core.transactions.*;
@@ -410,15 +415,15 @@ class NodeAttachmentServiceTest {
}
""".trimIndent()
val compiler = ToolProvider.getSystemJavaCompiler()
- val source = object : SimpleJavaFileObject(URI.create("string:///${packages.joinToString("/")}/${className}.java"), JavaFileObject.Kind.SOURCE) {
+ val source = object : SimpleJavaFileObject(URI.create("string:///${packages.joinToString("/")}/$className.java"), JavaFileObject.Kind.SOURCE) {
override fun getCharContent(ignoreEncodingErrors: Boolean): CharSequence {
return newClass
}
}
val fileManager = compiler.getStandardFileManager(null, null, null)
- fileManager.setLocation(StandardLocation.CLASS_OUTPUT, listOf(dir.toFile()))
+ fileManager.setLocation(StandardLocation.CLASS_OUTPUT, listOf(workingDir.toFile()))
- val compile = compiler.getTask(System.out.writer(), fileManager, null, null, null, listOf(source)).call()
+ compiler.getTask(System.out.writer(), fileManager, null, null, null, listOf(source)).call()
return Paths.get(fileManager.list(StandardLocation.CLASS_OUTPUT, "", setOf(JavaFileObject.Kind.CLASS), true).single().name)
}
}
diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt
index 5f60bb6618..320e499718 100644
--- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt
+++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt
@@ -69,6 +69,8 @@ class NetworkRegistrationHelperTest {
doReturn(null).whenever(it).tlsCertCrlDistPoint
doReturn(null).whenever(it).tlsCertCrlIssuer
doReturn(true).whenever(it).crlCheckSoftFail
+ doReturn(null).whenever(it).cryptoServiceName
+ doReturn(null).whenever(it).cryptoServiceConf
}
}
@@ -85,7 +87,7 @@ class NetworkRegistrationHelperTest {
val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate) }
- createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).buildKeystore()
+ createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).generateKeysAndRegister()
val nodeKeystore = config.signingCertificateStore.get()
val sslKeystore = config.p2pSslOptions.keyStore.get()
@@ -130,7 +132,7 @@ class NetworkRegistrationHelperTest {
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java)
- .isThrownBy { registrationHelper.buildKeystore() }
+ .isThrownBy { registrationHelper.generateKeysAndRegister() }
.withMessageContaining(CertificateType.TLS.toString())
}
@@ -141,7 +143,7 @@ class NetworkRegistrationHelperTest {
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java)
- .isThrownBy { registrationHelper.buildKeystore() }
+ .isThrownBy { registrationHelper.generateKeysAndRegister() }
.withMessageContaining(invalidName.toString())
}
@@ -156,7 +158,7 @@ class NetworkRegistrationHelperTest {
}
val registrationHelper = createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA)
- registrationHelper.buildKeystore()
+ registrationHelper.generateKeysAndRegister()
val trustStore = config.p2pSslOptions.trustStore.get()
trustStore.run {
assertTrue(contains(extraTrustedCertAlias))
@@ -174,7 +176,7 @@ class NetworkRegistrationHelperTest {
val registrationHelper = createRegistrationHelper()
assertThatThrownBy {
- registrationHelper.buildKeystore()
+ registrationHelper.generateKeysAndRegister()
}.isInstanceOf(CertPathValidatorException::class.java)
}
@@ -186,7 +188,7 @@ class NetworkRegistrationHelperTest {
val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate) }
- createRegistrationHelper(CertRole.SERVICE_IDENTITY, rootAndIntermediateCA).buildKeystore()
+ createRegistrationHelper(CertRole.SERVICE_IDENTITY, rootAndIntermediateCA).generateKeysAndRegister()
val nodeKeystore = config.signingCertificateStore.get()
@@ -251,10 +253,7 @@ class NetworkRegistrationHelperTest {
return when (certRole) {
CertRole.NODE_CA -> NodeRegistrationHelper(config, certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword))
CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper(
- config.certificatesDirectory,
- config.signingCertificateStore,
- config.myLegalName,
- config.emailAddress,
+ config,
certService,
config.certificatesDirectory / networkRootTrustStoreFileName,
networkRootTrustStorePassword,
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt
index 0e7bad2e20..13b4b334e6 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt
@@ -356,7 +356,7 @@ class DriverDSLImpl(
config.corda,
HTTPNetworkRegistrationService(networkServicesConfig, versionInfo),
NodeRegistrationOption(rootTruststorePath, rootTruststorePassword)
- ).buildKeystore()
+ ).generateKeysAndRegister()
config
}
} else {
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
index 929b40ac77..e7cbd4717c 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
@@ -38,6 +38,7 @@ import net.corda.node.services.config.*
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.node.services.keys.KeyManagementServiceInternal
+import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.node.services.messaging.Message
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.persistence.NodeAttachmentService
@@ -65,7 +66,7 @@ import rx.internal.schedulers.CachedThreadScheduler
import java.math.BigInteger
import java.nio.file.Path
import java.nio.file.Paths
-import java.security.KeyPair
+import java.security.PublicKey
import java.time.Clock
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
@@ -378,7 +379,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
}
override fun makeKeyManagementService(identityService: PersistentIdentityService): KeyManagementServiceInternal {
- return E2ETestKeyManagementService(identityService)
+ return E2ETestKeyManagementService(identityService, cryptoService)
}
override fun startShell() {
@@ -386,10 +387,13 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
}
// This is not thread safe, but node construction is done on a single thread, so that should always be fine
- override fun generateKeyPair(): KeyPair {
+ override fun generateKeyPair(alias: String): PublicKey {
+ require(cryptoService is BCCryptoService) { "MockNode supports BCCryptoService only, but it is ${cryptoService.javaClass.name}" }
counter = counter.add(BigInteger.ONE)
// The StartedMockNode specifically uses EdDSA keys as they are fixed and stored in json files for some tests (e.g IRSSimulation).
- return Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, counter)
+ val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, counter)
+ (cryptoService as BCCryptoService).importKey(alias, keyPair)
+ return keyPair.public
}
// NodeInfo requires a non-empty addresses list and so we give it a dummy value for mock nodes.
@@ -613,6 +617,8 @@ private fun mockNodeConfiguration(certificatesDirectory: Path): NodeConfiguratio
doReturn(FlowTimeoutConfiguration(1.hours, 3, backoffBase = 1.0)).whenever(it).flowTimeout
doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec
doReturn(null).whenever(it).devModeOptions
+ doReturn(null).whenever(it).cryptoServiceName
+ doReturn(null).whenever(it).cryptoServiceConf
doReturn(EnterpriseConfiguration(
mutualExclusionConfiguration = MutualExclusionConfiguration(false, "", 20000, 40000),
useMultiThreadedSMM = false
diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt
index e9f4902c58..99d60846b8 100644
--- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt
+++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt
@@ -13,7 +13,7 @@ fun testNetworkParameters(
modifiedTime: Instant = Instant.now(),
maxMessageSize: Int = 10485760,
// TODO: Make this configurable and consistence across driver, bootstrapper, demobench and NetworkMapServer
- maxTransactionSize: Int = maxMessageSize,
+ maxTransactionSize: Int = maxMessageSize * 50,
whitelistedContractImplementations: Map> = emptyMap(),
epoch: Int = 1,
eventHorizon: Duration = 30.days
diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/JarSignatureTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/JarSignatureTestUtils.kt
index 903d05fca7..fb13a38de8 100644
--- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/JarSignatureTestUtils.kt
+++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/JarSignatureTestUtils.kt
@@ -31,6 +31,10 @@ object JarSignatureTestUtils {
fun Path.createJar(fileName: String, vararg contents: String) =
executeProcess(*(arrayOf("jar", "cvf", fileName) + contents))
+ fun Path.addIndexList(fileName: String) {
+ executeProcess(*(arrayOf("jar", "i", fileName)))
+ }
+
fun Path.updateJar(fileName: String, vararg contents: String) =
executeProcess(*(arrayOf("jar", "uvf", fileName) + contents))
diff --git a/testing/test-utils/src/test/kotlin/net/corda/testing/core/JarSignatureCollectorTest.kt b/testing/test-utils/src/test/kotlin/net/corda/testing/core/JarSignatureCollectorTest.kt
index 3f356d5e7f..dd7fb5c90d 100644
--- a/testing/test-utils/src/test/kotlin/net/corda/testing/core/JarSignatureCollectorTest.kt
+++ b/testing/test-utils/src/test/kotlin/net/corda/testing/core/JarSignatureCollectorTest.kt
@@ -5,10 +5,12 @@ import net.corda.testing.core.JarSignatureTestUtils.generateKey
import net.corda.testing.core.JarSignatureTestUtils.getJarSigners
import net.corda.testing.core.JarSignatureTestUtils.signJar
import net.corda.testing.core.JarSignatureTestUtils.updateJar
+import net.corda.testing.core.JarSignatureTestUtils.addIndexList
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
+import net.corda.testing.core.CHARLIE_NAME
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.AfterClass
@@ -36,6 +38,7 @@ class JarSignatureCollectorTest {
fun beforeClass() {
dir.generateKey(ALICE, "storepass", ALICE_NAME.toString(), keyPassword = ALICE_PASS)
dir.generateKey(BOB, "storepass", BOB_NAME.toString(), keyPassword = BOB_PASS)
+ dir.generateKey(CHARLIE, "storepass", CHARLIE_NAME.toString(), "EC", CHARLIE_PASS)
(dir / "_signable1").writeLines(listOf("signable1"))
(dir / "_signable2").writeLines(listOf("signable2"))
@@ -134,12 +137,19 @@ class JarSignatureCollectorTest {
// and our JarSignatureCollector
@Test
fun `one signer with EC algorithm`() {
- dir.generateKey(CHARLIE, "storepass", CHARLIE_NAME.toString(), "EC", CHARLIE_PASS)
dir.createJar(FILENAME, "_signable1", "_signable2")
val key = dir.signJar(FILENAME, CHARLIE, "storepass", CHARLIE_PASS)
assertEquals(listOf(key), dir.getJarSigners(FILENAME)) // We only used CHARLIE's distinguished name, so the keys will be different.
}
+ @Test
+ fun `jar with jar index file`() {
+ dir.createJar(FILENAME, "_signable1")
+ dir.addIndexList(FILENAME)
+ val key = signAsAlice()
+ assertEquals(listOf(key), dir.getJarSigners(FILENAME))
+ }
+
private fun signAsAlice() = dir.signJar(FILENAME, ALICE, "storepass", ALICE_PASS)
private fun signAsBob() = dir.signJar(FILENAME, BOB, "storepass", BOB_PASS)
}
diff --git a/tools/bootstrapper/src/test/kotlin/net/corda/bootstrapper/PackageOwnerParsingTest.kt b/tools/bootstrapper/src/test/kotlin/net/corda/bootstrapper/PackageOwnerParsingTest.kt
index 3caf0285d3..cbc9b53cd4 100644
--- a/tools/bootstrapper/src/test/kotlin/net/corda/bootstrapper/PackageOwnerParsingTest.kt
+++ b/tools/bootstrapper/src/test/kotlin/net/corda/bootstrapper/PackageOwnerParsingTest.kt
@@ -8,10 +8,7 @@ import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.JarSignatureTestUtils.generateKey
import org.assertj.core.api.Assertions.assertThat
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.Rule
-import org.junit.Test
+import org.junit.*
import org.junit.rules.ExpectedException
import picocli.CommandLine
import java.nio.file.Files
@@ -124,6 +121,7 @@ class PackageOwnerParsingTest {
assertThat(networkBootstrapper.registerPackageOwnership).hasSize(3)
}
+ @Ignore("Ignoring this test as the delimiters don't work correctly, see CORDA-2191")
@Test
fun `parse registration request with delimiter inclusive passwords`() {
val aliceKeyStorePath1 = dirAlice / "_alicestore1"