CORDA-881: Signed network parameters has the network map cert attached to it instead of just the public key. (#2346)

Introduced DigitalSignatureWithCert and SignedDataWithCert as internal APIs, with the expectation that they will become public; renamed the network parameters end-point to network-parameters; updated the network-map.rst doc; and did some refactoring.
This commit is contained in:
Shams Asari
2018-01-12 07:59:08 +00:00
committed by GitHub
parent 0a56c75543
commit bbfbb08c43
21 changed files with 236 additions and 262 deletions

View File

@ -34,13 +34,8 @@ object DevIdentityGenerator {
override val trustStorePassword get() = throw NotImplementedError("Not expected to be called")
}
// TODO The passwords for the dev key stores are spread everywhere and should be constants in a single location
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
nodeSslConfig.certificatesDirectory.createDirectories()
nodeSslConfig.createDevKeyStores(rootCert, intermediateCa, legalName)
nodeSslConfig.createDevKeyStores(legalName)
val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword)
val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair())
@ -54,16 +49,12 @@ object DevIdentityGenerator {
val keyPairs = (1..dirs.size).map { generateKeyPair() }
val compositeKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
keyPairs.zip(dirs) { keyPair, nodeDir ->
val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, compositeKey).map { publicKey ->
X509Utilities.createCertificate(
CertificateType.SERVICE_IDENTITY,
intermediateCa.certificate,
intermediateCa.keyPair,
DEV_INTERMEDIATE_CA.certificate,
DEV_INTERMEDIATE_CA.keyPair,
notaryName.x500Principal,
publicKey)
}
@ -74,7 +65,7 @@ object DevIdentityGenerator {
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
keyPair.private,
"cordacadevkeypass".toCharArray(),
arrayOf(serviceKeyCert, intermediateCa.certificate, rootCert))
arrayOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
keystore.save(distServKeyStoreFile, "cordacadevpass")
}

View File

@ -9,12 +9,17 @@ import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import java.security.cert.X509Certificate
import javax.security.auth.x500.X500Principal
// TODO Merge this file and DevIdentityGenerator
/**
* Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
*/
fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) {
fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name,
rootCert: X509Certificate = DEV_ROOT_CA.certificate,
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) {
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
@ -39,6 +44,17 @@ fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateC
}
}
fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair {
val keyPair = Crypto.generateKeyPair()
val cert = X509Utilities.createCertificate(
CertificateType.NETWORK_MAP,
rootCa.certificate,
rootCa.keyPair,
X500Principal("CN=Network Map,O=R3 Ltd,L=London,C=GB"),
keyPair.public)
return CertificateAndKeyPair(cert, keyPair)
}
/**
* Create a dev node CA cert, as a sub-cert of the given [intermediateCa], and matching key pair using the given
* [CordaX500Name] as the cert subject.
@ -55,3 +71,16 @@ fun createDevNodeCa(intermediateCa: CertificateAndKeyPair, legalName: CordaX500N
nameConstraints = nameConstraints)
return CertificateAndKeyPair(cert, keyPair)
}
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_INTERMEDIATE_CA)
val DEV_ROOT_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_ROOT_CA)
// We need a class so that we can get hold of the class loader
internal object DevCaHelper {
fun loadDevCa(alias: String): CertificateAndKeyPair {
// TODO: Should be identity scheme
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
return caKeyStore.getCertificateAndKeyPair(alias, "cordacadevkeypass")
}
}

View File

@ -13,12 +13,14 @@ import java.security.SignatureException
* A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected
* to be in the same order as the identities.
*/
// TODO Move this to net.corda.nodeapi.internal.network
// TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key
// that the node owns. This check can only be done by the network map server as it can check with the doorman if a node
// is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite
// public keys.
@CordaSerializable
class SignedNodeInfo(val raw: SerializedBytes<NodeInfo>, val signatures: List<DigitalSignature>) {
// TODO Add root cert param (or TrustAnchor) to make sure all the identities belong to the same root
fun verified(): NodeInfo {
val nodeInfo = raw.deserialize()
val identities = nodeInfo.legalIdentities.filterNot { it.owningKey is CompositeKey }

View File

@ -1,16 +1,12 @@
package net.corda.nodeapi.internal.network
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.verify
import net.corda.core.identity.Party
import net.corda.core.internal.CertRole
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.nodeapi.internal.crypto.X509Utilities
import java.security.SignatureException
import java.security.cert.CertPathValidatorException
import java.security.cert.X509Certificate
import java.time.Instant
@ -55,28 +51,8 @@ data class NetworkParameters(
@CordaSerializable
data class NotaryInfo(val identity: Party, val validating: Boolean)
/**
* A serialized [NetworkMap] and its signature and certificate. Enforces signature validity in order to deserialize the data
* contained within.
*/
@CordaSerializable
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val signature: DigitalSignatureWithCert) {
/**
* Return the deserialized NetworkMap if the signature and certificate can be verified.
*
* @throws CertPathValidatorException if the certificate path is invalid.
* @throws SignatureException if the signature is invalid.
*/
@Throws(SignatureException::class, CertPathValidatorException::class)
fun verified(trustedRoot: X509Certificate): NetworkMap {
signature.by.publicKey.verify(raw.bytes, signature)
// Assume network map cert is under the default trust root.
X509Utilities.validateCertificateChain(trustedRoot, signature.by, trustedRoot)
return raw.deserialize()
}
}
// TODO: This class should reside in the [DigitalSignature] class.
// TODO: Removing the val from signatureBytes causes serialisation issues
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T {
require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" }
X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert)
return verified()
}

View File

@ -1,35 +1,32 @@
package net.corda.nodeapi.internal.network
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.internal.copyTo
import net.corda.core.internal.div
import net.corda.core.internal.signWithCert
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.security.KeyPair
class NetworkParametersCopier(
networkParameters: NetworkParameters,
signingKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(),
overwriteFile: Boolean = false
) {
private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
private val serializedNetworkParameters = networkParameters.let {
val serialize = it.serialize()
val signature = signingKeyPair.sign(serialize)
SignedData(serialize, signature).serialize()
}
private val serialisedSignedNetParams = networkParameters.signWithCert(
networkMapCa.keyPair.private,
networkMapCa.certificate
).serialize()
fun install(nodeDir: Path) {
try {
serializedNetworkParameters.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
serialisedSignedNetParams.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
} catch (e: FileAlreadyExistsException) {
// This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
// ignore this exception as we're happy with the existing file.
}
}
}
}

View File

@ -168,7 +168,7 @@ class X509UtilitiesTest {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
sslConfig.createDevKeyStores(MEGA_CORP.name, rootCa.certificate, intermediateCa)
// Load back server certificate
val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword)
@ -203,7 +203,7 @@ class X509UtilitiesTest {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
sslConfig.createDevKeyStores(MEGA_CORP.name, rootCa.certificate, intermediateCa)
sslConfig.createTrustStore(rootCa.certificate)
val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)