Merge pull request #1545 from corda/merges/os-merge-to-6f00560

OS -> ENT merge to 6f00560
This commit is contained in:
szymonsztuka 2018-11-07 13:28:07 +00:00 committed by GitHub
commit 7961124bca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 868 additions and 296 deletions

View File

@ -1259,6 +1259,8 @@ public final class net.corda.core.crypto.Crypto extends java.lang.Object
@NotNull @NotNull
public static final java.security.Provider findProvider(String) public static final java.security.Provider findProvider(String)
@NotNull @NotNull
public static final net.corda.core.crypto.SignatureScheme findSignatureScheme(int)
@NotNull
public static final net.corda.core.crypto.SignatureScheme findSignatureScheme(String) public static final net.corda.core.crypto.SignatureScheme findSignatureScheme(String)
@NotNull @NotNull
public static final net.corda.core.crypto.SignatureScheme findSignatureScheme(java.security.PrivateKey) public static final net.corda.core.crypto.SignatureScheme findSignatureScheme(java.security.PrivateKey)

4
.idea/compiler.xml generated
View File

@ -106,6 +106,10 @@
<module name="core_main" target="1.8" /> <module name="core_main" target="1.8" />
<module name="core_smokeTest" target="1.8" /> <module name="core_smokeTest" target="1.8" />
<module name="core_test" target="1.8" /> <module name="core_test" target="1.8" />
<module name="crypto-service_main" target="1.8" />
<module name="crypto-service_test" target="1.8" />
<module name="crypto-services_main" target="1.8" />
<module name="crypto-services_test" target="1.8" />
<module name="data_main" target="1.8" /> <module name="data_main" target="1.8" />
<module name="data_test" target="1.8" /> <module name="data_test" target="1.8" />
<module name="dbmigration_main" target="1.8" /> <module name="dbmigration_main" target="1.8" />

View File

@ -167,6 +167,9 @@ allprojects {
suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml' suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml'
cveValidForHours = 1 cveValidForHours = 1
format = 'ALL' 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 sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8

View File

@ -12,7 +12,7 @@
<Appenders> <Appenders>
<Console name="Console-Appender" target="SYSTEM_OUT"> <Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout> <PatternLayout>
<ScriptPatternSelector defaultPattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg%n%throwable{short.message}}{INFO=white,WARN=red,FATAL=bright red}"> <ScriptPatternSelector defaultPattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg%n%throwable{short.message}%n}{INFO=white,WARN=red,FATAL=bright red}">
<Script name="MDCSelector" language="javascript"><![CDATA[ <Script name="MDCSelector" language="javascript"><![CDATA[
result = null; result = null;
if (!logEvent.getContextData().size() == 0) { if (!logEvent.getContextData().size() == 0) {
@ -23,7 +23,7 @@
result; result;
]]> ]]>
</Script> </Script>
<PatternMatch key="WithMDC" pattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg %X%n%throwable{short.message}}{INFO=white,WARN=red,FATAL=bright red}"/> <PatternMatch key="WithMDC" pattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg %X%n%throwable{short.message}%n}{INFO=white,WARN=red,FATAL=bright red}"/>
</ScriptPatternSelector> </ScriptPatternSelector>
</PatternLayout> </PatternLayout>
</Console> </Console>

View File

@ -199,6 +199,12 @@ object Crypto {
+ signatureSchemeMap.values.map { Pair(it.signatureOID, it) }) + signatureSchemeMap.values.map { Pair(it.signatureOID, it) })
.toMap() .toMap()
/**
* Map of supported digital signature schemes associated by [SignatureScheme.schemeNumberID].
* SchemeNumberID is the scheme identifier attached to [SignatureMetadata].
*/
private val signatureSchemeNumberIDMap: Map<Int, SignatureScheme> = Crypto.supportedSignatureSchemes().associateBy { it.schemeNumberID }
@JvmStatic @JvmStatic
fun supportedSignatureSchemes(): List<SignatureScheme> = ArrayList(signatureSchemeMap.values) fun supportedSignatureSchemes(): List<SignatureScheme> = ArrayList(signatureSchemeMap.values)
@ -224,6 +230,13 @@ object Crypto {
?: throw IllegalArgumentException("Unrecognised algorithm: ${algorithm.algorithm.id}") ?: 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]. * Factory pattern to retrieve the corresponding [SignatureScheme] based on [SignatureScheme.schemeCodeName].
* This function is usually called by key generators and verify signature functions. * This function is usually called by key generators and verify signature functions.
@ -458,9 +471,14 @@ object Crypto {
@JvmStatic @JvmStatic
@Throws(InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun doSign(keyPair: KeyPair, signableData: SignableData): TransactionSignature { fun doSign(keyPair: KeyPair, signableData: SignableData): TransactionSignature {
val sigKey: SignatureScheme = findSignatureScheme(keyPair.private) val sigKey: SignatureScheme = Crypto.findSignatureScheme(keyPair.private)
val sigMetaData: SignatureScheme = findSignatureScheme(keyPair.public) val sigMetaData: SignatureScheme = Crypto.findSignatureScheme(signableData.signatureMetadata.schemeNumberID)
require(sigKey == sigMetaData) { // 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}." "Metadata schemeCodeName: ${sigMetaData.schemeCodeName} is not aligned with the key type: ${sigKey.schemeCodeName}."
} }
val signatureBytes = doSign(sigKey.schemeCodeName, keyPair.private, signableData.serialize().bytes) val signatureBytes = doSign(sigKey.schemeCodeName, keyPair.private, signableData.serialize().bytes)

View File

@ -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"
}

View File

@ -14,9 +14,10 @@ object JarSignatureCollector {
/** /**
* @see <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File> * @see <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File>
* 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 * 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,
* and Java Security Manager. */ * 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-.*)".toRegex() */
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]. * Returns an ordered list of every [Party] which has signed every signable item in the given [JarInputStream].

View File

@ -86,7 +86,6 @@ data class NetworkParameters(
require(epoch > 0) { "epoch must be at least 1" } require(epoch > 0) { "epoch must be at least 1" }
require(maxMessageSize > 0) { "maxMessageSize 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 > 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(!eventHorizon.isNegative) { "eventHorizon must be positive value" }
require(noOverlap(packageOwnership.keys)) { "multiple packages added to the packageOwnership overlap." } require(noOverlap(packageOwnership.keys)) { "multiple packages added to the packageOwnership overlap." }
} }

View File

@ -21,18 +21,24 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo 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.BCECPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECKey import org.bouncycastle.jce.interfaces.ECKey
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec 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.BCSphincs256PrivateKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotEquals
import org.junit.Test import org.junit.Test
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyPair
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.cert.X509Certificate
import java.util.* import java.util.*
import kotlin.test.* import kotlin.test.*
@ -936,4 +942,32 @@ class CryptoUtilsTest {
// Just in case, test that signatures of different messages are not the same. // Just in case, test that signatures of different messages are not the same.
assertNotEquals(OpaqueBytes(signedData1stTime), OpaqueBytes(signedZeroArray1stTime)) 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)
}
} }

View File

@ -1,3 +1,5 @@
kotlin.incremental=true kotlin.incremental=true
org.gradle.jvmargs=-XX:+UseG1GC -Xmx1g -Dfile.encoding=UTF-8 org.gradle.jvmargs=-XX:+UseG1GC -Xmx1g -Dfile.encoding=UTF-8
org.gradle.caching=true org.gradle.caching=true
owasp.failOnError=false
owasp.failBuildOnCVSS=11.0

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.config package net.corda.nodeapi.internal.config
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.internal.outputStream import net.corda.core.internal.outputStream
import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
@ -8,6 +9,7 @@ import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.nio.file.OpenOption import java.nio.file.OpenOption
import java.nio.file.Path import java.nio.file.Path
import java.security.PrivateKey
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
interface CertificateStore : Iterable<Pair<String, X509Certificate>> { interface CertificateStore : Iterable<Pair<String, X509Certificate>> {
@ -42,7 +44,6 @@ interface CertificateStore : Iterable<Pair<String, X509Certificate>> {
} }
operator fun set(alias: String, certificate: X509Certificate) { operator fun set(alias: String, certificate: X509Certificate) {
update { update {
internal.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, certificate) internal.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, certificate)
} }
@ -65,7 +66,6 @@ interface CertificateStore : Iterable<Pair<String, X509Certificate>> {
* @throws IllegalArgumentException if no certificate for the alias is found, or if the certificate is not an [X509Certificate]. * @throws IllegalArgumentException if no certificate for the alias is found, or if the certificate is not an [X509Certificate].
*/ */
operator fun get(alias: String): X509Certificate { operator fun get(alias: String): X509Certificate {
return query { return query {
getCertificate(alias) getCertificate(alias)
} }
@ -79,6 +79,21 @@ interface CertificateStore : Iterable<Pair<String, X509Certificate>> {
this@CertificateStore.forEach(::setCertificate) this@CertificateStore.forEach(::setCertificate)
} }
} }
fun setCertPathOnly(alias: String, certificates: List<X509Certificate>) {
// 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 private class DelegatingCertificateStore(override val value: X509KeyStore, override val password: String, override val entryPassword: String) : CertificateStore

View File

@ -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. // 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 { class FileBasedCertificateStoreSupplier(val path: Path, val storePassword: String, val entryPassword: String) : CertificateStoreSupplier {
override fun get(createNew: Boolean) = CertificateStore.fromFile(path, storePassword, entryPassword, createNew) override fun get(createNew: Boolean) = CertificateStore.fromFile(path, storePassword, entryPassword, createNew)
} }

View File

@ -16,7 +16,6 @@ interface SslConfiguration {
} }
interface MutualSslConfiguration : SslConfiguration { interface MutualSslConfiguration : SslConfiguration {
override val keyStore: FileBasedCertificateStoreSupplier override val keyStore: FileBasedCertificateStoreSupplier
override val trustStore: FileBasedCertificateStoreSupplier override val trustStore: FileBasedCertificateStoreSupplier
} }

View File

@ -10,8 +10,8 @@ import java.security.SecureRandom
import java.security.Signature import java.security.Signature
/** /**
* Provide extra OID look up for signature algorithm not supported by bouncy castle. * Provide extra OID look up for signature algorithm not supported by BouncyCastle.
* This builder will use bouncy castle's JcaContentSignerBuilder as fallback for unknown algorithm. * This builder will use BouncyCastle's JcaContentSignerBuilder as fallback for unknown algorithm.
*/ */
object ContentSignerBuilder { object ContentSignerBuilder {
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner { fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner {

View File

@ -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) }
}

View File

@ -7,14 +7,15 @@ import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore import java.security.KeyStore
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
/** /**
* Wrapper around a [KeyStore] object but only dealing with [X509Certificate]s and with a better API. * 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. */ /** 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. */ /** Create an empty [KeyStore] using the given password. [save] is not supported. */
constructor(storePassword: String) : this( 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 { fun getCertificateAndKeyPair(alias: String, keyPassword: String): CertificateAndKeyPair {
val cert = getCertificate(alias) val cert = getCertificate(alias)
val publicKey = Crypto.toSupportedPublicKey(cert.publicKey) val publicKey = getPublicKey(alias)
return CertificateAndKeyPair(cert, KeyPair(publicKey, getPrivateKey(alias, keyPassword))) 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 { fun getPrivateKey(alias: String, keyPassword: String): PrivateKey {
return internal.getSupportedKey(alias, keyPassword) return internal.getSupportedKey(alias, keyPassword)
} }
fun setPrivateKey(alias: String, key: PrivateKey, certificates: List<X509Certificate>, keyPassword: String) { fun setPrivateKey(alias: String, key: PrivateKey, certificates: List<X509Certificate>, keyPassword: String) {
internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray()) internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray())
save()
} }
fun setCertificate(alias: String, certificate: X509Certificate) { fun setCertificate(alias: String, certificate: X509Certificate) {
internal.setCertificateEntry(alias, certificate) internal.setCertificateEntry(alias, certificate)
save()
} }
fun save() { fun save() {
internal.save(checkWritableToFile(), storePassword) if (saveSupported) {
internal.save(checkWritableToFile(), storePassword)
}
} }
fun update(action: X509KeyStore.() -> Unit) { fun update(action: X509KeyStore.() -> Unit) {

View File

@ -301,24 +301,21 @@ object X509Utilities {
/** /**
* Create certificate signing request using provided information. * Create certificate signing request using provided information.
*/ */
private fun createCertificateSigningRequest(subject: X500Principal, fun createCertificateSigningRequest(subject: X500Principal, email: String, publicKey: PublicKey, contentSigner: ContentSigner, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest {
email: String, return JcaPKCS10CertificationRequestBuilder(subject, publicKey)
keyPair: KeyPair,
signatureScheme: SignatureScheme,
certRole: CertRole): PKCS10CertificationRequest {
val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.findProvider(signatureScheme.providerName))
return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public)
.addAttribute(BCStyle.E, DERUTF8String(email)) .addAttribute(BCStyle.E, DERUTF8String(email))
.addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole) .addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole)
.build(signer).apply { .build(contentSigner).apply {
if (!isSignatureValid()) { if (!isSignatureValid()) {
throw SignatureException("The certificate signing request signature validation failed.") throw SignatureException("The certificate signing request signature validation failed.")
} }
} }
} }
fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest { 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<X509Certificate>): CertPath { fun buildCertPath(first: X509Certificate, remaining: List<X509Certificate>): CertPath {
@ -356,7 +353,7 @@ object X509Utilities {
val CertRole.certificateType: CertificateType get() = CertificateType.values().first { it.role == this } 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. * NOTE: To avoid unnecessary copying use [X509Certificate] where possible.
*/ */
@ -376,7 +373,7 @@ val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certif
val Array<Certificate>.x509: List<X509Certificate> get() = map { it.x509 } val Array<Certificate>.x509: List<X509Certificate> get() = map { it.x509 }
/** /**
* Validates the signature of the CSR * Validates the signature of the CSR.
*/ */
fun PKCS10CertificationRequest.isSignatureValid(): Boolean { fun PKCS10CertificationRequest.isSignatureValid(): Boolean {
return this.isSignatureValid(JcaContentVerifierProviderBuilder().build(this.subjectPublicKeyInfo)) return this.isSignatureValid(JcaContentVerifierProviderBuilder().build(this.subjectPublicKeyInfo))

View File

@ -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
}

View File

@ -394,7 +394,7 @@ internal constructor(private val initSerEnv: Boolean,
notaries = notaryInfos, notaries = notaryInfos,
modifiedTime = Instant.now(), modifiedTime = Instant.now(),
maxMessageSize = 10485760, maxMessageSize = 10485760,
maxTransactionSize = 10485760, maxTransactionSize = 524288000,
whitelistedContractImplementations = whitelist, whitelistedContractImplementations = whitelist,
packageOwnership = packageOwnership.filterNotNullValues(), packageOwnership = packageOwnership.filterNotNullValues(),
epoch = 1, epoch = 1,

View File

@ -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<IllegalArgumentException>("Unrecognised algorithm: 2.26.40086077608615255153862931087626791001") {
signingCertStore.query { getPrivateKey(alias, "entrypassword") }
}
}
}

View File

@ -95,7 +95,7 @@ class NodeRegistrationTest : IntegrationTest() {
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"), cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"),
notaryCustomOverrides = mapOf("devMode" to false) notaryCustomOverrides = mapOf("devMode" to false)
) { ) {
val (alice, genevieve) = listOf( val (alice, genevieve) = listOf(
startNode(providedName = aliceName, customOverrides = mapOf("devMode" to false)), startNode(providedName = aliceName, customOverrides = mapOf("devMode" to false)),
startNode(providedName = genevieveName, customOverrides = mapOf("devMode" to false)) startNode(providedName = genevieveName, customOverrides = mapOf("devMode" to false))
).transpose().getOrThrow() ).transpose().getOrThrow()

View File

@ -9,22 +9,20 @@ import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.CordaException import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext 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.SecureHash
import net.corda.core.crypto.isCRLDistributionPointBlacklisted import net.corda.core.crypto.isCRLDistributionPointBlacklisted
import net.corda.core.crypto.newSecureRandom import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sign
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.*
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.* import net.corda.core.messaging.*
import net.corda.core.node.* import net.corda.core.node.*
import net.corda.core.node.services.* 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.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService 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.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.DeduplicationHandler
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.NetworkMapClient 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.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.CertificateStore 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
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA 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_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA 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.DISTRIBUTED_NOTARY_ALIAS_PREFIX
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX
import net.corda.nodeapi.internal.persistence.* import net.corda.nodeapi.internal.persistence.*
import net.corda.nodeapi.internal.storeLegalIdentity
import net.corda.tools.shell.InteractiveShell import net.corda.tools.shell.InteractiveShell
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
@ -165,6 +165,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize() val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize()
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) } val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize() val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
val cryptoService = configuration.makeCryptoService()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize() val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
@Suppress("LeakingThis") @Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize() val keyManagementService = makeKeyManagementService(identityService).tokenize()
@ -260,6 +261,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private fun initKeyStores(): X509Certificate { private fun initKeyStores(): X509Certificate {
if (configuration.devMode) { if (configuration.devMode) {
configuration.configureWithDevSSLCertificate() 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() return validateKeyStores()
} }
@ -298,7 +304,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
log.info("Node starting up ...") log.info("Node starting up ...")
val trustRoot = initKeyStores() val trustRoot = initKeyStores()
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
initialiseJVMAgents() initialiseJVMAgents()
schemaService.mappedSchemasWarnings().forEach { schemaService.mappedSchemasWarnings().forEach {
@ -324,6 +329,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
startDatabase() startDatabase()
val (identity, identityKeyPair) = obtainIdentity() val (identity, identityKeyPair) = obtainIdentity()
X509Utilities.validateCertPath(trustRoot, identity.certPath)
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
identityService.start(trustRoot, listOf(identity.certificate, nodeCa)) identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
val mutualExclusionConfiguration = configuration.enterpriseConfiguration.mutualExclusionConfiguration val mutualExclusionConfiguration = configuration.enterpriseConfiguration.mutualExclusionConfiguration
@ -357,6 +365,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
attachments.start() attachments.start()
cordappProvider.start(netParams.whitelistedContractImplementations) cordappProvider.start(netParams.whitelistedContractImplementations)
nodeProperties.start() 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) keyManagementService.start(keyPairs)
val notaryService = makeNotaryService(myNotaryIdentity) val notaryService = makeNotaryService(myNotaryIdentity)
installCordaServices(myNotaryIdentity) installCordaServices(myNotaryIdentity)
@ -434,7 +445,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val nodeInfoFromDb = getPreviousNodeInfoIfPresent(identity) val nodeInfoFromDb = getPreviousNodeInfoIfPresent(identity)
val nodeInfo = if (potentialNodeInfo == nodeInfoFromDb?.copy(serial = 0)) { 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. // The node info hasn't changed. We use the one from the database to preserve the serial.
log.debug("Node-info hasn't changed") log.debug("Node-info hasn't changed")
@ -449,7 +459,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val nodeInfoAndSigned = NodeInfoAndSigned(nodeInfo) { publicKey, serialised -> val nodeInfoAndSigned = NodeInfoAndSigned(nodeInfo) { publicKey, serialised ->
val privateKey = keyPairs.single { it.public == publicKey }.private 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. // 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<S>(val configuration: NodeConfiguration,
log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " + log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " +
ServiceHub::class.java.name) ServiceHub::class.java.name)
} catch (e: ServiceInstantiationException) { } 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) { } catch (e: Exception) {
log.error("Unable to install Corda service ${it.name}", e) log.error("Unable to install Corda service ${it.name}", e)
} }
@ -664,9 +678,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return try { return try {
// The following will throw IOException if key file not found or KeyStoreException if keystore password is incorrect. // The following will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeyStore = configuration.p2pSslOptions.keyStore.get() val sslKeyStore = configuration.p2pSslOptions.keyStore.get()
val identitiesKeyStore = configuration.signingCertificateStore.get() val signingCertificateStore = configuration.signingCertificateStore.get()
val trustStore = configuration.p2pSslOptions.trustStore.get() val trustStore = configuration.p2pSslOptions.trustStore.get()
AllCertificateStores(trustStore, sslKeyStore, identitiesKeyStore) AllCertificateStores(trustStore, sslKeyStore, signingCertificateStore)
} catch (e: IOException) { } catch (e: IOException) {
log.error("IO exception while trying to validate keystores and truststore", e) log.error("IO exception while trying to validate keystores and truststore", e)
null null
@ -788,7 +802,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // 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 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. // 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() { open fun stop() {
@ -813,50 +827,47 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
nodeInfo: NodeInfo, nodeInfo: NodeInfo,
myNotaryIdentity: PartyAndCertificate?, myNotaryIdentity: PartyAndCertificate?,
networkParameters: NetworkParameters) 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<PartyAndCertificate, KeyPair> { private fun obtainIdentity(): Pair<PartyAndCertificate, KeyPair> {
val keyStore = configuration.signingCertificateStore.get() val legalIdentityPrivateKeyAlias = "$NODE_IDENTITY_ALIAS_PREFIX-private-key"
val legalName = configuration.myLegalName
// TODO: Integrate with Key management service? if (!cryptoService.containsKey(legalIdentityPrivateKeyAlias)) {
val privateKeyAlias = "$NODE_IDENTITY_ALIAS_PREFIX-private-key" log.info("$legalIdentityPrivateKeyAlias not found in key store, generating fresh key!")
if (privateKeyAlias !in keyStore) { storeLegalIdentity(legalIdentityPrivateKeyAlias)
log.info("$privateKeyAlias not found in key store, generating fresh key!")
keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair())
} }
val signingCertificateStore = configuration.signingCertificateStore.get()
val (x509Cert, keyPair) = keyStore.query { getCertificateAndKeyPair(privateKeyAlias, keyStore.entryPassword) } val x509Cert = signingCertificateStore.query { getCertificate(legalIdentityPrivateKeyAlias) }
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity. // 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<X509Certificate> = signingCertificateStore.query { getCertificateChain(legalIdentityPrivateKeyAlias) }
check(certificates.first() == x509Cert) { check(certificates.first() == x509Cert) {
"Certificates from key store do not line up!" "Certificates from key store do not line up!"
} }
val subject = CordaX500Name.build(certificates.first().subjectX500Principal) val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
val legalName = configuration.myLegalName
if (subject != legalName) { if (subject != legalName) {
throw ConfigurationException("The name '$legalName' for $NODE_IDENTITY_ALIAS_PREFIX doesn't match what's in the key store: $subject") 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 getPartyAndCertificatePlusAliasKeyPair(certificates, legalIdentityPrivateKeyAlias)
return Pair(PartyAndCertificate(certPath), keyPair)
} }
/** Loads pre-generated notary service cluster identity. */ /** Loads pre-generated notary service cluster identity. */
private fun loadNotaryClusterIdentity(serviceLegalName: CordaX500Name): Pair<PartyAndCertificate, KeyPair> { private fun loadNotaryClusterIdentity(serviceLegalName: CordaX500Name): Pair<PartyAndCertificate, KeyPair> {
val keyStore = configuration.signingCertificateStore.get()
val privateKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key" 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 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 // 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 + // 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. // 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.") } 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) val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
@ -864,11 +875,43 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
throw ConfigurationException("The name of the notary service '$serviceLegalName' for $DISTRIBUTED_NOTARY_ALIAS_PREFIX doesn't " + 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`.") "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<PartyAndCertificate, KeyPair>, where KeyPair uses an AliasPrivateKey.
private fun getPartyAndCertificatePlusAliasKeyPair(certificates: List<X509Certificate>, privateKeyAlias: String): Pair<PartyAndCertificate, KeyPair> {
val certPath = X509Utilities.buildCertPath(certificates) val certPath = X509Utilities.buildCertPath(certificates)
val keyPair = KeyPair(cryptoService.getPublicKey(privateKeyAlias), AliasPrivateKey(privateKeyAlias))
return Pair(PartyAndCertificate(certPath), keyPair) 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, protected open fun makeVaultService(keyManagementService: KeyManagementService,
services: ServicesForResolution, services: ServicesForResolution,

View File

@ -71,7 +71,7 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
HTTPNetworkRegistrationService( HTTPNetworkRegistrationService(
requireNotNull(conf.networkServices), requireNotNull(conf.networkServices),
versionInfo), versionInfo),
nodeRegistration).buildKeystore() nodeRegistration).generateKeysAndRegister()
// Minimal changes to make registration tool create node identity. // Minimal changes to make registration tool create node identity.
// TODO: Move node identity generation logic from node to registration helper. // 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) initialRegistration(node.configuration)
} }
} }

View File

@ -82,10 +82,11 @@ object ConfigHelper {
* Strictly for dev only automatically construct a server certificate/private key signed from * 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. * 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) 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) { fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path) {
val specifiedTrustStore = trustStore.getOptional() val specifiedTrustStore = trustStore.getOptional()
@ -100,7 +101,7 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
loadDevCaTrustStore().copyTo(trustStore.get(true)) 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) } 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) } FileBasedCertificateStoreSupplier(keyStore.path, keyStore.storePassword, keyStore.entryPassword).get(true).also { it.registerDevP2pCertificates(myLegalName) }

View File

@ -10,11 +10,14 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.config.rpc.NodeRpcOptions 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.BrokerRpcSslOptions
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.nodeapi.internal.config.* 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.CordaPersistence.DataSourceConfigTag
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.tools.shell.SSHDConfiguration import net.corda.tools.shell.SSHDConfiguration
import org.slf4j.Logger import org.slf4j.Logger
import java.net.URL import java.net.URL
@ -79,6 +82,8 @@ interface NodeConfiguration {
val baseDirectory: Path val baseDirectory: Path
val certificatesDirectory: 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 signingCertificateStore: FileBasedCertificateStoreSupplier
val p2pSslOptions: MutualSslConfiguration val p2pSslOptions: MutualSslConfiguration
@ -88,6 +93,11 @@ interface NodeConfiguration {
val cordappSignerKeyFingerprintBlacklist: List<String> val cordappSignerKeyFingerprintBlacklist: List<String>
// 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<String> fun validate(): List<String>
companion object { companion object {
@ -106,6 +116,13 @@ interface NodeConfiguration {
val defaultJmxReporterType = JmxReporterType.JOLOKIA 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<FlowOverride> = listOf()) data class FlowOverrideConfig(val overrides: List<FlowOverride> = listOf())
@ -240,11 +257,13 @@ data class NodeConfigurationImpl(
override val enableSNI: Boolean = true, override val enableSNI: Boolean = true,
private val useOpenSsl: Boolean = false, private val useOpenSsl: Boolean = false,
override val flowOverrides: FlowOverrideConfig?, override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() } override val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() },
override val cryptoServiceName: SupportedCryptoServices? = null,
override val cryptoServiceConf: String? = null
) : NodeConfiguration { ) : NodeConfiguration {
companion object { companion object {
private val logger = loggerFor<NodeConfigurationImpl>() private val logger = loggerFor<NodeConfigurationImpl>()
// private val supportedCryptoServiceNames = setOf("BC", "UTIMACO", "GEMALTO-LUNA", "AZURE-KEY-VAULT")
} }
private val actualRpcSettings: NodeRpcSettings private val actualRpcSettings: NodeRpcSettings
@ -297,6 +316,14 @@ data class NodeConfigurationImpl(
return errors return errors
} }
private fun validateCryptoService(): List<String> {
val errors = mutableListOf<String>()
if (cryptoServiceName == null && cryptoServiceConf != null) {
errors += "cryptoServiceName is null, but cryptoServiceConf is set to $cryptoServiceConf"
}
return errors
}
override fun validate(): List<String> { override fun validate(): List<String> {
val errors = mutableListOf<String>() val errors = mutableListOf<String>()
errors += validateDevModeOptions() errors += validateDevModeOptions()
@ -309,6 +336,7 @@ data class NodeConfigurationImpl(
errors += validateTlsCertCrlConfig() errors += validateTlsCertCrlConfig()
errors += validateNetworkServices() errors += validateNetworkServices()
errors += validateH2Settings() errors += validateH2Settings()
errors += validateCryptoService()
return errors return errors
} }

View File

@ -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<PublicKey, PrivateKey, PersistentKey, String> {
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<PublicKey, String>()
// A map for anonymous keys.
private val keysMap = createKeyMap(cacheFactory)
override fun start(initialKeyPairs: Set<KeyPair>) {
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<PublicKey> 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<PublicKey>): Iterable<PublicKey> = 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)
}
}
}

View File

@ -5,6 +5,9 @@ import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SingletonSerializeAsToken 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 org.bouncycastle.operator.ContentSigner
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
@ -24,7 +27,7 @@ import javax.annotation.concurrent.ThreadSafe
* etc. * etc.
*/ */
@ThreadSafe @ThreadSafe
class E2ETestKeyManagementService(val identityService: IdentityService) : SingletonSerializeAsToken(), KeyManagementServiceInternal { class E2ETestKeyManagementService(val identityService: IdentityService, private val cryptoService: CryptoService? = null) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
private class InnerState { private class InnerState {
val keys = HashMap<PublicKey, PrivateKey>() val keys = HashMap<PublicKey, PrivateKey>()
} }
@ -32,12 +35,20 @@ class E2ETestKeyManagementService(val identityService: IdentityService) : Single
private val mutex = ThreadBox(InnerState()) private val mutex = ThreadBox(InnerState())
// Accessing this map clones it. // Accessing this map clones it.
override val keys: Set<PublicKey> get() = mutex.locked { keys.keys } override val keys: Set<PublicKey> get() = mutex.locked { keys.keys }
// Maintain a map from PublicKey to alias for the initial keys.
val keyPairs: Set<KeyPair> get() = mutex.locked { keys.map { KeyPair(it.key, it.value) }.toSet() } val keyPairs: Set<KeyPair> get() = mutex.locked { keys.map { KeyPair(it.key, it.value) }.toSet() }
override fun start(initialKeyPairs: Set<KeyPair>) { override fun start(initialKeyPairs: Set<KeyPair>) {
mutex.locked { mutex.locked {
for (key in initialKeyPairs) { 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
} }
} }
} }

View File

@ -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) }
}
}

View File

@ -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.
}

View File

@ -12,6 +12,7 @@ import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.SchemaService.SchemaOptions import net.corda.node.services.api.SchemaService.SchemaOptions
import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.identity.PersistentIdentityService 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.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.P2PMessageDeduplicator import net.corda.node.services.messaging.P2PMessageDeduplicator
import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.persistence.DBCheckpointStorage
@ -35,6 +36,7 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
object NodeCoreV1 : MappedSchema(schemaFamily = NodeCore.javaClass, version = 1, object NodeCoreV1 : MappedSchema(schemaFamily = NodeCore.javaClass, version = 1,
mappedTypes = listOf(DBCheckpointStorage.DBCheckpoint::class.java, mappedTypes = listOf(DBCheckpointStorage.DBCheckpoint::class.java,
DBTransactionStorage.DBTransaction::class.java, DBTransactionStorage.DBTransaction::class.java,
BasicHSMKeyManagementService.PersistentKey::class.java,
PersistentKeyManagementService.PersistentKey::class.java, PersistentKeyManagementService.PersistentKey::class.java,
NodeSchedulerService.PersistentScheduledState::class.java, NodeSchedulerService.PersistentScheduledState::class.java,
NodeAttachmentService.DBAttachment::class.java, NodeAttachmentService.DBAttachment::class.java,

View File

@ -56,6 +56,7 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
name == "DeduplicationChecker_watermark" -> caffeine name == "DeduplicationChecker_watermark" -> caffeine
name == "BFTNonValidatingNotaryService_transactions" -> caffeine.maximumSize(defaultCacheSize) name == "BFTNonValidatingNotaryService_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "RaftUniquenessProvider_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?") else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?")
} }
} }

View File

@ -1,28 +1,31 @@
package net.corda.node.utilities.registration package net.corda.node.utilities.registration
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.NodeRegistrationOption import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration 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.CertificateStore
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
import net.corda.nodeapi.internal.crypto.CertificateType 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.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities 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_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS 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.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.util.io.pem.PemObject import org.bouncycastle.util.io.pem.PemObject
import java.io.IOException import java.io.IOException
import java.io.StringWriter import java.io.StringWriter
import java.net.ConnectException import java.net.ConnectException
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Duration 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 * Helper for managing the node registration process, which checks for any existing certificates and requests them if
* needed. * needed.
*/ */
// TODO: Use content signer instead of keypairs. open class NetworkRegistrationHelper(
open class NetworkRegistrationHelper(private val certificatesDirectory: Path, config: NodeConfiguration,
private val signingCertificateStore: CertificateStoreSupplier, private val certService: NetworkRegistrationService,
private val myLegalName: CordaX500Name, private val networkRootTrustStorePath: Path,
private val emailAddress: String, networkRootTrustStorePassword: String,
private val certService: NetworkRegistrationService, private val nodeCaKeyAlias: String,
private val networkRootTrustStorePath: Path, private val certRole: CertRole,
networkRootTrustStorePassword: String, private val nextIdleDuration: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))
private val keyAlias: String, ) {
private val certRole: CertRole,
private val nextIdleDuration: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) {
companion object { companion object {
const val SELF_SIGNED_PRIVATE_KEY = "SelfSignedPrivateKey" const val SELF_SIGNED_PRIVATE_KEY = "SelfSignedPrivateKey"
val logger = contextLogger() 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" private val requestIdStore = certificatesDirectory / "certificate-request-id.txt"
protected val rootTrustStore: X509KeyStore protected val rootTrustStore: X509KeyStore
protected val rootCert: X509Certificate 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. * 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 * 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 * 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 request has been approved, and then the certificate chain will be downloaded and stored in [certificateStore].
* the certificates directory.
* *
* @throws CertificateRequestException if the certificate retrieved by doorman is invalid. * @throws CertificateRequestException if the certificate retrieved by doorman is invalid.
*/ */
fun buildKeystore() { fun generateKeysAndRegister() {
certificatesDirectory.createDirectories() certificatesDirectory.createDirectories()
val nodeKeyStore = signingCertificateStore.get(createNew = true) // We need this in case cryptoService and certificateStore share the same KeyStore (for backwards compatibility purposes).
if (keyAlias in nodeKeyStore) { // 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...") println("Certificate already exists, Corda node will now terminate...")
return 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() val tlsCrlIssuerCert = validateAndGetTlsCrlIssuerCert()
if (tlsCrlIssuerCert == null && isTlsCrlIssuerCertRequired()) { 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. 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. | 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()) | The node will now terminate.""".trimMargin())
throw IllegalArgumentException("TLS CRL issuer certificate not found in the trust store.") throw IllegalArgumentException("TLS CRL issuer certificate not found in the trust store.")
} }
return tlsCrlIssuerCert
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()
} }
private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List<X509Certificate>) { private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List<X509Certificate>) {
@ -149,18 +164,6 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
println("Certificate signing request approved, storing private key with the certificate chain.") println("Certificate signing request approved, storing private key with the certificate chain.")
} }
private fun storePrivateKeyWithCertificates(nodeKeystore: CertificateStore, keyPair: KeyPair, certificates: List<X509Certificate>, 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 { private fun CertificateStore.loadOrCreateKeyPair(alias: String, entryPassword: String = password): KeyPair {
// Create or load self signed keypair from the key store. // Create or load self signed keypair from the key store.
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
@ -184,65 +187,80 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
* @return List of certificate chain. * @return List of certificate chain.
*/ */
private fun pollServerForCertificates(requestId: String): List<X509Certificate> { private fun pollServerForCertificates(requestId: String): List<X509Certificate> {
println("Start polling server for certificate signing approval.") try {
// Poll server to download the signed certificate once request has been approved. println("Start polling server for certificate signing approval.")
var idlePeriodDuration: Duration? = null // Poll server to download the signed certificate once request has been approved.
while (true) { var idlePeriodDuration: Duration? = null
try { while (true) {
val (pollInterval, certificates) = certService.retrieveCertificates(requestId) try {
if (certificates != null) { val (pollInterval, certificates) = certService.retrieveCertificates(requestId)
return certificates if (certificates != null) {
} return certificates
Thread.sleep(pollInterval.toMillis()) }
} catch (e: ServiceUnavailableException) { Thread.sleep(pollInterval.toMillis())
idlePeriodDuration = nextIdleDuration(idlePeriodDuration) } catch (e: ServiceUnavailableException) {
if (idlePeriodDuration != null) { idlePeriodDuration = nextIdleDuration(idlePeriodDuration)
Thread.sleep(idlePeriodDuration.toMillis()) if (idlePeriodDuration != null) {
} else { Thread.sleep(idlePeriodDuration.toMillis())
throw NodeRegistrationException("Compatibility Zone registration service is currently unavailable, " } else {
+ "try again later!.", e) 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 * 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. * @return Request ID return from the server.
*/ */
private fun submitOrResumeCertificateSigningRequest(keyPair: KeyPair): String { private fun submitOrResumeCertificateSigningRequest(publicKey: PublicKey, contentSigner: ContentSigner): String {
// Retrieve request id from file if exists, else post a request to server. try {
return if (!requestIdStore.exists()) { // Retrieve request id from file if exists, else post a request to server.
val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, keyPair, certRole) return if (!requestIdStore.exists()) {
val writer = StringWriter() val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, publicKey, contentSigner, certRole)
JcaPEMWriter(writer).use { val writer = StringWriter()
it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded)) 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.") } catch (e: Exception) {
println() throw if (e is ConnectException || e is ServiceUnavailableException || e is IOException) {
println("Legal Name: $myLegalName") NodeRegistrationException(e.message, e)
println("Email: $emailAddress") } else e
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
} }
} }
protected open fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) {} protected open fun onSuccess(publicKey: PublicKey, contentSigner: ContentSigner, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) {}
protected open fun validateAndGetTlsCrlIssuerCert(): X509Certificate? = null protected open fun validateAndGetTlsCrlIssuerCert(): X509Certificate? = null
@ -256,14 +274,9 @@ class NodeRegistrationException(
class NodeRegistrationHelper( class NodeRegistrationHelper(
private val config: NodeConfiguration, private val config: NodeConfiguration,
certService: NetworkRegistrationService, certService: NetworkRegistrationService, regConfig: NodeRegistrationOption, computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) :
regConfig: NodeRegistrationOption, NetworkRegistrationHelper(
computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1)) config,
) : NetworkRegistrationHelper(
config.certificatesDirectory,
config.signingCertificateStore,
config.myLegalName,
config.emailAddress,
certService, certService,
regConfig.networkRootTrustStorePath, regConfig.networkRootTrustStorePath,
regConfig.networkRootTrustStorePassword, regConfig.networkRootTrustStorePassword,
@ -275,29 +288,38 @@ class NodeRegistrationHelper(
val logger = contextLogger() val logger = contextLogger()
} }
override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) { override fun onSuccess(publicKey: PublicKey, contentSigner: ContentSigner, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) {
createSSLKeystore(nodeCAKeyPair, certificates, tlsCrlCertificateIssuer) createSSLKeystore(publicKey, contentSigner, certificates, tlsCrlCertificateIssuer)
createTruststore(certificates.last()) createTruststore(certificates.last())
} }
private fun createSSLKeystore(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>, tlsCertCrlIssuer: X500Name?) { private fun createSSLKeystore(nodeCaPublicKey: PublicKey, nodeCaContentSigner: ContentSigner, nodeCaCertificateChain: List<X509Certificate>, tlsCertCrlIssuer: X500Name?) {
val keyStore = config.p2pSslOptions.keyStore val keyStore = config.p2pSslOptions.keyStore
val certificateStore = keyStore.get(createNew = true) val certificateStore = keyStore.get(createNew = true)
certificateStore.update { certificateStore.update {
println("Generating SSL certificate for node messaging service.") println("Generating SSL certificate for node messaging service.")
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) 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( val sslCert = X509Utilities.createCertificate(
CertificateType.TLS, CertificateType.TLS,
certificates.first(), issuerCertificate.subjectX500Principal,
nodeCAKeyPair, nodeCaPublicKey,
nodeCaContentSigner,
config.myLegalName.x500Principal, config.myLegalName.x500Principal,
sslKeyPair.public, sslKeyPair.public,
validityWindow,
crlDistPoint = config.tlsCertCrlDistPoint?.toString(), crlDistPoint = config.tlsCertCrlDistPoint?.toString(),
crlIssuer = tlsCertCrlIssuer) crlIssuer = tlsCertCrlIssuer)
logger.info("Generated TLS certificate: $sslCert") logger.info("Generated TLS certificate: $sslCert")
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates, certificateStore.entryPassword)
val sslCertificateChain: List<X509Certificate> = 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) { private fun createTruststore(rootCertificate: X509Certificate) {

View File

@ -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 @Test
fun `package ownership checks are correct`() { fun `package ownership checks are correct`() {
val key1 = generateKeyPair().public val key1 = generateKeyPair().public

View File

@ -139,12 +139,11 @@ class NodeConfigurationImplTest {
private fun getConfig(cfgName: String, overrides: Config = ConfigFactory.empty()): Config { private fun getConfig(cfgName: String, overrides: Config = ConfigFactory.empty()): Config {
val path = this::class.java.classLoader.getResource(cfgName).toPath() val path = this::class.java.classLoader.getResource(cfgName).toPath()
val cfg = ConfigHelper.loadConfig( return ConfigHelper.loadConfig(
baseDirectory = path.parent, baseDirectory = path.parent,
configFile = path, configFile = path,
configOverrides = overrides configOverrides = overrides
) )
return cfg
} }
@Test @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 @Test
fun `rpcAddress and rpcSettings_address are equivalent`() { fun `rpcAddress and rpcSettings_address are equivalent`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
@ -247,7 +264,7 @@ class NodeConfigurationImplTest {
@Test @Test
fun `jmxReporterType is null and defaults to Jokolia`() { 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() val nodeConfig = rawConfig.parseAsNodeConfiguration()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString()) assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
} }

View File

@ -35,6 +35,7 @@ import net.corda.testing.node.internal.startFlow
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.junit.* import org.junit.*
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.OutputStream import java.io.OutputStream
import java.net.URI import java.net.URI
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -80,25 +81,30 @@ class NodeAttachmentServiceTest {
@After @After
fun tearDown() { fun tearDown() {
dir.list { subdir ->
subdir.forEach(Path::deleteRecursively)
}
database.close() database.close()
} }
@Test @Test
fun `importing a signed jar saves the signers to the storage`() { fun `importing a signed jar saves the signers to the storage`() {
val jarAndSigner = makeTestSignedContractJar("com.example.MyContract") SelfCleaningDir().use { file ->
val signedJar = jarAndSigner.first val jarAndSigner = makeTestSignedContractJar(file.path, "com.example.MyContract")
val attachmentId = storage.importAttachment(signedJar.inputStream(), "test", null) val signedJar = jarAndSigner.first
assertEquals(listOf(jarAndSigner.second.hash), storage.openAttachment(attachmentId)!!.signers.map { it.hash }) signedJar.inputStream().use { jarStream ->
val attachmentId = storage.importAttachment(jarStream, "test", null)
assertEquals(listOf(jarAndSigner.second.hash), storage.openAttachment(attachmentId)!!.signers.map { it.hash })
}
}
} }
@Test @Test
fun `importing a non-signed jar will save no signers`() { fun `importing a non-signed jar will save no signers`() {
val jarName = makeTestContractJar("com.example.MyContract") SelfCleaningDir().use {
val attachmentId = storage.importAttachment(dir.resolve(jarName).inputStream(), "test", null) val jarName = makeTestContractJar(it.path, "com.example.MyContract")
assertEquals(0, storage.openAttachment(attachmentId)!!.signers.size) it.path.resolve(jarName).inputStream().use { jarStream ->
val attachmentId = storage.importAttachment(jarStream, "test", null)
assertEquals(0, storage.openAttachment(attachmentId)!!.signers.size)
}
}
} }
@Test @Test
@ -127,25 +133,27 @@ class NodeAttachmentServiceTest {
@Test @Test
fun `insert contract attachment as an untrusted uploader and then as trusted CorDapp uploader`() { fun `insert contract attachment as an untrusted uploader and then as trusted CorDapp uploader`() {
val contractJarName = makeTestContractJar("com.example.MyContract") SelfCleaningDir().use { file ->
val testJar = dir.resolve(contractJarName) val contractJarName = makeTestContractJar(file.path, "com.example.MyContract")
val expectedHash = testJar.readAll().sha256() val testJar = file.path.resolve(contractJarName)
val expectedHash = testJar.readAll().sha256()
// PRIVILEGED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, P2P_UPLOADER, UNKNOWN_UPLOADER) // PRIVILEGED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, P2P_UPLOADER, UNKNOWN_UPLOADER)
// TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER) // TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER)
database.transaction { database.transaction {
val id = testJar.read { storage.privilegedImportOrGetAttachment(it, P2P_UPLOADER, null) } val id = testJar.read { storage.privilegedImportOrGetAttachment(it, P2P_UPLOADER, null) }
assertEquals(expectedHash, id) assertEquals(expectedHash, id)
val attachment1 = storage.openAttachment(expectedHash) val attachment1 = storage.openAttachment(expectedHash)
val id2 = testJar.read { storage.privilegedImportOrGetAttachment(it, DEPLOYED_CORDAPP_UPLOADER, null) } val id2 = testJar.read { storage.privilegedImportOrGetAttachment(it, DEPLOYED_CORDAPP_UPLOADER, null) }
assertEquals(expectedHash, id2) assertEquals(expectedHash, id2)
val attachment2 = storage.openAttachment(expectedHash) val attachment2 = storage.openAttachment(expectedHash)
assertNotEquals(attachment1, attachment2) assertNotEquals(attachment1, attachment2)
assertEquals(P2P_UPLOADER, (attachment1 as ContractAttachment).uploader) assertEquals(P2P_UPLOADER, (attachment1 as ContractAttachment).uploader)
assertEquals(DEPLOYED_CORDAPP_UPLOADER, (attachment2 as ContractAttachment).uploader) assertEquals(DEPLOYED_CORDAPP_UPLOADER, (attachment2 as ContractAttachment).uploader)
}
} }
} }
@ -350,20 +358,17 @@ class NodeAttachmentServiceTest {
return Pair(file, file.readAll().sha256()) 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 { 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<Pair<String, String>> = emptyList()) { private fun makeTestJar(output: OutputStream, extraEntries: List<Pair<String, String>> = emptyList()) {
output.use { output.use {
val jar = JarOutputStream(it) val jar = JarOutputStream(it)
@ -372,33 +377,33 @@ class NodeAttachmentServiceTest {
jar.closeEntry() jar.closeEntry()
jar.putNextEntry(JarEntry("test2.txt")) jar.putNextEntry(JarEntry("test2.txt"))
jar.write("Some more useful content".toByteArray()) jar.write("Some more useful content".toByteArray())
extraEntries.forEach { extraEntries.forEach { entry ->
jar.putNextEntry(JarEntry(it.first)) jar.putNextEntry(JarEntry(entry.first))
jar.write(it.second.toByteArray()) jar.write(entry.second.toByteArray())
} }
jar.closeEntry() jar.closeEntry()
} }
} }
private fun makeTestSignedContractJar(contractName: String): Pair<Path, PublicKey> { private fun makeTestSignedContractJar(workingDir: Path, contractName: String): Pair<Path, PublicKey> {
val alias = "testAlias" val alias = "testAlias"
val pwd = "testPassword" val pwd = "testPassword"
dir.generateKey(alias, pwd, ALICE_NAME.toString()) workingDir.generateKey(alias, pwd, ALICE_NAME.toString())
val jarName = makeTestContractJar(contractName) val jarName = makeTestContractJar(workingDir, contractName)
val signer = dir.signJar(jarName, alias, pwd) val signer = workingDir.signJar(jarName, alias, pwd)
return dir.resolve(jarName) to signer 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 packages = contractName.split(".")
val jarName = "testattachment.jar" val jarName = "testattachment.jar"
val className = packages.last() val className = packages.last()
createTestClass(className, packages.subList(0, packages.size - 1)) createTestClass(workingDir, className, packages.subList(0, packages.size - 1))
dir.createJar(jarName, "${contractName.replace(".", "/")}.class") workingDir.createJar(jarName, "${contractName.replace(".", "/")}.class")
return jarName return jarName
} }
private fun createTestClass(className: String, packages: List<String>): Path { private fun createTestClass(workingDir: Path, className: String, packages: List<String>): Path {
val newClass = """package ${packages.joinToString(".")}; val newClass = """package ${packages.joinToString(".")};
import net.corda.core.contracts.*; import net.corda.core.contracts.*;
import net.corda.core.transactions.*; import net.corda.core.transactions.*;
@ -410,15 +415,15 @@ class NodeAttachmentServiceTest {
} }
""".trimIndent() """.trimIndent()
val compiler = ToolProvider.getSystemJavaCompiler() 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 { override fun getCharContent(ignoreEncodingErrors: Boolean): CharSequence {
return newClass return newClass
} }
} }
val fileManager = compiler.getStandardFileManager(null, null, null) 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) return Paths.get(fileManager.list(StandardLocation.CLASS_OUTPUT, "", setOf(JavaFileObject.Kind.CLASS), true).single().name)
} }
} }

View File

@ -69,6 +69,8 @@ class NetworkRegistrationHelperTest {
doReturn(null).whenever(it).tlsCertCrlDistPoint doReturn(null).whenever(it).tlsCertCrlDistPoint
doReturn(null).whenever(it).tlsCertCrlIssuer doReturn(null).whenever(it).tlsCertCrlIssuer
doReturn(true).whenever(it).crlCheckSoftFail 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) } 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 nodeKeystore = config.signingCertificateStore.get()
val sslKeystore = config.p2pSslOptions.keyStore.get() val sslKeystore = config.p2pSslOptions.keyStore.get()
@ -130,7 +132,7 @@ class NetworkRegistrationHelperTest {
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last()) saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java) assertThatExceptionOfType(CertificateRequestException::class.java)
.isThrownBy { registrationHelper.buildKeystore() } .isThrownBy { registrationHelper.generateKeysAndRegister() }
.withMessageContaining(CertificateType.TLS.toString()) .withMessageContaining(CertificateType.TLS.toString())
} }
@ -141,7 +143,7 @@ class NetworkRegistrationHelperTest {
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last()) saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java) assertThatExceptionOfType(CertificateRequestException::class.java)
.isThrownBy { registrationHelper.buildKeystore() } .isThrownBy { registrationHelper.generateKeysAndRegister() }
.withMessageContaining(invalidName.toString()) .withMessageContaining(invalidName.toString())
} }
@ -156,7 +158,7 @@ class NetworkRegistrationHelperTest {
} }
val registrationHelper = createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA) val registrationHelper = createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA)
registrationHelper.buildKeystore() registrationHelper.generateKeysAndRegister()
val trustStore = config.p2pSslOptions.trustStore.get() val trustStore = config.p2pSslOptions.trustStore.get()
trustStore.run { trustStore.run {
assertTrue(contains(extraTrustedCertAlias)) assertTrue(contains(extraTrustedCertAlias))
@ -174,7 +176,7 @@ class NetworkRegistrationHelperTest {
val registrationHelper = createRegistrationHelper() val registrationHelper = createRegistrationHelper()
assertThatThrownBy { assertThatThrownBy {
registrationHelper.buildKeystore() registrationHelper.generateKeysAndRegister()
}.isInstanceOf(CertPathValidatorException::class.java) }.isInstanceOf(CertPathValidatorException::class.java)
} }
@ -186,7 +188,7 @@ class NetworkRegistrationHelperTest {
val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate) } 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() val nodeKeystore = config.signingCertificateStore.get()
@ -251,10 +253,7 @@ class NetworkRegistrationHelperTest {
return when (certRole) { return when (certRole) {
CertRole.NODE_CA -> NodeRegistrationHelper(config, certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword)) CertRole.NODE_CA -> NodeRegistrationHelper(config, certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword))
CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper( CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper(
config.certificatesDirectory, config,
config.signingCertificateStore,
config.myLegalName,
config.emailAddress,
certService, certService,
config.certificatesDirectory / networkRootTrustStoreFileName, config.certificatesDirectory / networkRootTrustStoreFileName,
networkRootTrustStorePassword, networkRootTrustStorePassword,

View File

@ -356,7 +356,7 @@ class DriverDSLImpl(
config.corda, config.corda,
HTTPNetworkRegistrationService(networkServicesConfig, versionInfo), HTTPNetworkRegistrationService(networkServicesConfig, versionInfo),
NodeRegistrationOption(rootTruststorePath, rootTruststorePassword) NodeRegistrationOption(rootTruststorePath, rootTruststorePassword)
).buildKeystore() ).generateKeysAndRegister()
config config
} }
} else { } else {

View File

@ -38,6 +38,7 @@ import net.corda.node.services.config.*
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.node.services.keys.KeyManagementServiceInternal 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.Message
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
@ -65,7 +66,7 @@ import rx.internal.schedulers.CachedThreadScheduler
import java.math.BigInteger import java.math.BigInteger
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.security.KeyPair import java.security.PublicKey
import java.time.Clock import java.time.Clock
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -378,7 +379,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
} }
override fun makeKeyManagementService(identityService: PersistentIdentityService): KeyManagementServiceInternal { override fun makeKeyManagementService(identityService: PersistentIdentityService): KeyManagementServiceInternal {
return E2ETestKeyManagementService(identityService) return E2ETestKeyManagementService(identityService, cryptoService)
} }
override fun startShell() { 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 // 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) 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). // 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. // 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(FlowTimeoutConfiguration(1.hours, 3, backoffBase = 1.0)).whenever(it).flowTimeout
doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec
doReturn(null).whenever(it).devModeOptions doReturn(null).whenever(it).devModeOptions
doReturn(null).whenever(it).cryptoServiceName
doReturn(null).whenever(it).cryptoServiceConf
doReturn(EnterpriseConfiguration( doReturn(EnterpriseConfiguration(
mutualExclusionConfiguration = MutualExclusionConfiguration(false, "", 20000, 40000), mutualExclusionConfiguration = MutualExclusionConfiguration(false, "", 20000, 40000),
useMultiThreadedSMM = false useMultiThreadedSMM = false

View File

@ -13,7 +13,7 @@ fun testNetworkParameters(
modifiedTime: Instant = Instant.now(), modifiedTime: Instant = Instant.now(),
maxMessageSize: Int = 10485760, maxMessageSize: Int = 10485760,
// TODO: Make this configurable and consistence across driver, bootstrapper, demobench and NetworkMapServer // TODO: Make this configurable and consistence across driver, bootstrapper, demobench and NetworkMapServer
maxTransactionSize: Int = maxMessageSize, maxTransactionSize: Int = maxMessageSize * 50,
whitelistedContractImplementations: Map<String, List<AttachmentId>> = emptyMap(), whitelistedContractImplementations: Map<String, List<AttachmentId>> = emptyMap(),
epoch: Int = 1, epoch: Int = 1,
eventHorizon: Duration = 30.days eventHorizon: Duration = 30.days

View File

@ -31,6 +31,10 @@ object JarSignatureTestUtils {
fun Path.createJar(fileName: String, vararg contents: String) = fun Path.createJar(fileName: String, vararg contents: String) =
executeProcess(*(arrayOf("jar", "cvf", fileName) + contents)) executeProcess(*(arrayOf("jar", "cvf", fileName) + contents))
fun Path.addIndexList(fileName: String) {
executeProcess(*(arrayOf("jar", "i", fileName)))
}
fun Path.updateJar(fileName: String, vararg contents: String) = fun Path.updateJar(fileName: String, vararg contents: String) =
executeProcess(*(arrayOf("jar", "uvf", fileName) + contents)) executeProcess(*(arrayOf("jar", "uvf", fileName) + contents))

View File

@ -5,10 +5,12 @@ import net.corda.testing.core.JarSignatureTestUtils.generateKey
import net.corda.testing.core.JarSignatureTestUtils.getJarSigners import net.corda.testing.core.JarSignatureTestUtils.getJarSigners
import net.corda.testing.core.JarSignatureTestUtils.signJar import net.corda.testing.core.JarSignatureTestUtils.signJar
import net.corda.testing.core.JarSignatureTestUtils.updateJar import net.corda.testing.core.JarSignatureTestUtils.updateJar
import net.corda.testing.core.JarSignatureTestUtils.addIndexList
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.AfterClass import org.junit.AfterClass
@ -36,6 +38,7 @@ class JarSignatureCollectorTest {
fun beforeClass() { fun beforeClass() {
dir.generateKey(ALICE, "storepass", ALICE_NAME.toString(), keyPassword = ALICE_PASS) dir.generateKey(ALICE, "storepass", ALICE_NAME.toString(), keyPassword = ALICE_PASS)
dir.generateKey(BOB, "storepass", BOB_NAME.toString(), keyPassword = BOB_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 / "_signable1").writeLines(listOf("signable1"))
(dir / "_signable2").writeLines(listOf("signable2")) (dir / "_signable2").writeLines(listOf("signable2"))
@ -134,12 +137,19 @@ class JarSignatureCollectorTest {
// and our JarSignatureCollector // and our JarSignatureCollector
@Test @Test
fun `one signer with EC algorithm`() { fun `one signer with EC algorithm`() {
dir.generateKey(CHARLIE, "storepass", CHARLIE_NAME.toString(), "EC", CHARLIE_PASS)
dir.createJar(FILENAME, "_signable1", "_signable2") dir.createJar(FILENAME, "_signable1", "_signable2")
val key = dir.signJar(FILENAME, CHARLIE, "storepass", CHARLIE_PASS) 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. 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 signAsAlice() = dir.signJar(FILENAME, ALICE, "storepass", ALICE_PASS)
private fun signAsBob() = dir.signJar(FILENAME, BOB, "storepass", BOB_PASS) private fun signAsBob() = dir.signJar(FILENAME, BOB, "storepass", BOB_PASS)
} }

View File

@ -8,10 +8,7 @@ import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.JarSignatureTestUtils.generateKey import net.corda.testing.core.JarSignatureTestUtils.generateKey
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.AfterClass import org.junit.*
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException import org.junit.rules.ExpectedException
import picocli.CommandLine import picocli.CommandLine
import java.nio.file.Files import java.nio.file.Files
@ -124,6 +121,7 @@ class PackageOwnerParsingTest {
assertThat(networkBootstrapper.registerPackageOwnership).hasSize(3) assertThat(networkBootstrapper.registerPackageOwnership).hasSize(3)
} }
@Ignore("Ignoring this test as the delimiters don't work correctly, see CORDA-2191")
@Test @Test
fun `parse registration request with delimiter inclusive passwords`() { fun `parse registration request with delimiter inclusive passwords`() {
val aliceKeyStorePath1 = dirAlice / "_alicestore1" val aliceKeyStorePath1 = dirAlice / "_alicestore1"