mirror of
https://github.com/corda/corda.git
synced 2025-01-26 06:09:25 +00:00
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:
parent
0a56c75543
commit
bbfbb08c43
1
.gitignore
vendored
1
.gitignore
vendored
@ -37,6 +37,7 @@ lib/quasar.jar
|
||||
.idea/markdown-navigator
|
||||
.idea/runConfigurations
|
||||
.idea/dictionaries
|
||||
.idea/codeStyles/
|
||||
/gradle-plugins/.idea/
|
||||
|
||||
# Include the -parameters compiler option by default in IntelliJ required for serialization.
|
||||
|
@ -407,7 +407,7 @@ object Crypto {
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(InvalidKeyException::class, SignatureException::class)
|
||||
fun doSign(privateKey: PrivateKey, clearData: ByteArray) = doSign(findSignatureScheme(privateKey), privateKey, clearData)
|
||||
fun doSign(privateKey: PrivateKey, clearData: ByteArray): ByteArray = doSign(findSignatureScheme(privateKey), privateKey, clearData)
|
||||
|
||||
/**
|
||||
* Generic way to sign [ByteArray] data with a [PrivateKey] and a known schemeCodeName [String].
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.verify
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
// TODO: Rename this to DigitalSignature.WithCert once we're happy for it to be public API. The methods will need documentation
|
||||
// and the correct exceptions will be need to be annotated
|
||||
/** A digital signature with attached certificate of the public key. */
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, bytes: ByteArray) : DigitalSignature(bytes) {
|
||||
fun verify(content: ByteArray): Boolean = by.publicKey.verify(content, this)
|
||||
fun verify(content: OpaqueBytes): Boolean = verify(content.bytes)
|
||||
}
|
||||
|
||||
/** Similar to [SignedData] but instead of just attaching the public key, the certificate for the key is attached instead. */
|
||||
@CordaSerializable
|
||||
class SignedDataWithCert<T : Any>(val raw: SerializedBytes<T>, val sig: DigitalSignatureWithCert) {
|
||||
fun verified(): T {
|
||||
sig.verify(raw)
|
||||
return uncheckedCast(raw.deserialize<Any>())
|
||||
}
|
||||
}
|
@ -3,11 +3,14 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
@ -27,6 +30,8 @@ import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets.UTF_8
|
||||
import java.nio.file.*
|
||||
import java.nio.file.attribute.FileAttribute
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.time.temporal.Temporal
|
||||
import java.util.*
|
||||
@ -308,6 +313,19 @@ fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, seri
|
||||
val KClass<*>.packageName: String get() = java.`package`.name
|
||||
|
||||
fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection
|
||||
|
||||
fun HttpURLConnection.checkOkResponse() {
|
||||
if (responseCode != 200) {
|
||||
val message = errorStream.use { it.reader().readText() }
|
||||
throw IOException("Response Code $responseCode: $message")
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> HttpURLConnection.responseAs(): T {
|
||||
checkOkResponse()
|
||||
return inputStream.use { it.readBytes() }.deserialize()
|
||||
}
|
||||
|
||||
/** Analogous to [Thread.join]. */
|
||||
fun ExecutorService.join() {
|
||||
shutdown() // Do not change to shutdownNow, tests use this method to assert the executor has no more tasks.
|
||||
@ -336,3 +354,9 @@ val CordaX500Name.x500Name: X500Name
|
||||
@VisibleForTesting
|
||||
val CordaX500Name.Companion.unspecifiedCountry
|
||||
get() = "ZZ"
|
||||
|
||||
fun <T : Any> T.signWithCert(privateKey: PrivateKey, certificate: X509Certificate): SignedDataWithCert<T> {
|
||||
val serialised = serialize()
|
||||
val signature = Crypto.doSign(privateKey, serialised.bytes)
|
||||
return SignedDataWithCert(serialised, DigitalSignatureWithCert(certificate, signature))
|
||||
}
|
||||
|
@ -1,84 +1,78 @@
|
||||
Network Map
|
||||
===========
|
||||
|
||||
The network map stores a collection of ``NodeInfo`` objects, each representing another node with which the node can interact.
|
||||
There are two sources from which a Corda node can retrieve ``NodeInfo`` objects:
|
||||
The network map is a collection of signed ``NodeInfo`` objects (signed by the node it represents and thus tamper-proof)
|
||||
forming the set of reachable nodes in a compatbility zone. A node can receive these objects from two sources:
|
||||
|
||||
1. the REST protocol with the network map service, which also provides a publishing API,
|
||||
1. The HTTP network map service if the ``compatibilityZoneURL`` config key is specified.
|
||||
2. The ``additional-node-infos`` directory within the node's directory.
|
||||
|
||||
2. the ``additional-node-infos`` directory.
|
||||
HTTP network map service
|
||||
------------------------
|
||||
|
||||
If the node is configured with the ``compatibilityZoneURL`` config then it first uploads its own signed ``NodeInfo``
|
||||
to the server (and each time it changes on startup) and then proceeds to download the entire network map. The network map
|
||||
consists of a list of ``NodeInfo`` hashes. The node periodically polls for the network map (based on the HTTP cache expiry
|
||||
header) and any new hash entries are downloaded and cached. Entries which no longer exist are deleted from the node's cache.
|
||||
|
||||
Protocol Design
|
||||
---------------
|
||||
The node info publishing protocol:
|
||||
The set of REST end-points for the network map service are as follows.
|
||||
|
||||
* Create a ``NodeInfo`` object, and sign it to create a ``SignedNodeInfo`` object.
|
||||
|
||||
* Serialise the signed data and POST the data to the network map server.
|
||||
|
||||
* The network map server validates the signature and acknowledges the registration with a HTTP 200 response, it will return HTTP 400 "Bad Request" if the data failed validation or if the public key wasn't registered with the network.
|
||||
|
||||
* The network map server will sign and distribute the new network map periodically.
|
||||
|
||||
Node side network map update protocol:
|
||||
|
||||
* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header.
|
||||
|
||||
* The network map service returns a signed ``NetworkMap`` object which looks as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
data class NetworkMap {
|
||||
val nodeInfoHashes: List<SecureHash>,
|
||||
val networkParametersHash: SecureHash
|
||||
}
|
||||
|
||||
The object contains list of node info hashes and hash of the network parameters data structure (without the signatures).
|
||||
|
||||
* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``.
|
||||
|
||||
Network Map service REST API:
|
||||
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| Request method | Path | Description |
|
||||
+================+===================================+========================================================================================================================================================+
|
||||
| POST | /network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and ``NetworkParameters`` hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /network-map/parameters/{hash} | Retrieve ``NetworkParameters`` object with the same hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
TODO: Access control of the network map will be added in the future.
|
||||
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| Request method | Path | Description |
|
||||
+================+=========================================+==============================================================================================================================================+
|
||||
| POST | /network-map/publish | For the node to upload its signed ``NodeInfo`` object to the network map. |
|
||||
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /network-map | Retrieve the current signed network map object. The entire object is signed with the network map certificate which is also attached. |
|
||||
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /network-map/node-info/{hash} | Retrieve a signed ``NodeInfo`` as specified in the network map object. |
|
||||
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /network-map/network-parameters/{hash} | Retrieve the signed network parameters (see below). The entire object is signed with the network map certificate which is also attached. |
|
||||
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
|
||||
The ``additional-node-infos`` directory
|
||||
---------------------------------------
|
||||
Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory.
|
||||
|
||||
Nodes expect to find a serialized ``SignedNodeInfo`` object, the same object which is sent to network map server.
|
||||
|
||||
Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string.
|
||||
|
||||
Hence if an operator wants node A to see node B they can pick B's ``NodeInfo`` file from B base directory and drop it into A's ``additional-node-infos`` directory.
|
||||
Alongside the HTTP network map service, or as a replacement if the node isn't connected to one, the node polls the
|
||||
contents of the ``additional-node-infos`` directory located in its base directory. Each file is expected to be the same
|
||||
signed ``NodeInfo`` object that the network map service vends. These are automtically added to the node's cache and can
|
||||
be used to supplement or replace the HTTP network map. If the same node is advertised through both mechanisms then the
|
||||
latest one is taken.
|
||||
|
||||
On startup the node generates its own signed node info file, filename of the format ``nodeInfo-${hash}``. To create a simple
|
||||
network without the HTTP network map service then simply place this file in the ``additional-node-infos`` directory
|
||||
of every node that's part of this network.
|
||||
|
||||
Network parameters
|
||||
------------------
|
||||
Network parameters are constants that every node participating in the network needs to agree on and use for interop purposes.
|
||||
The structure is distributed as a file containing serialized ``SignedData<NetworkParameters>`` with a signature from
|
||||
a sub-key of the compatibility zone root cert. Network map advertises the hash of currently used network parameters.
|
||||
The ``NetworkParameters`` structure contains:
|
||||
* ``minimumPlatformVersion`` - minimum version of Corda platform that is required for nodes in the network.
|
||||
* ``notaries`` - list of well known and trusted notary identities with information on validation type.
|
||||
* ``maxMessageSize`` - maximum P2P message size sent over the wire in bytes.
|
||||
* ``maxTransactionSize`` - maximum permitted transaction size in bytes.
|
||||
* ``modifiedTime`` - the time the network parameters were created by the CZ operator.
|
||||
* ``epoch`` - version number of the network parameters. Starting from 1, this will always increment on each new set of parameters.
|
||||
|
||||
The set of parameters is still under development and we may find the need to add additional fields.
|
||||
Network parameters are a set of values that every node participating in the network needs to agree on and use to
|
||||
correctly interoperate with each other. If the node is using the HTTP network map service then on first startup it will
|
||||
download the signed network parameters, cache it in a ``network-parameters`` file and apply them on the node.
|
||||
|
||||
.. warning:: If the ``network-parameters`` file is changed and no longer matches what the network map service is advertising
|
||||
then the node will automatically shutdown. Resolution to this is to delete the incorrect file and restart the node so
|
||||
that the parameters can be downloaded again.
|
||||
|
||||
.. note:: A future release will support the notion of network parameters changes.
|
||||
|
||||
If the node isn't using a HTTP network map service then it's expected the signed file is provided by some other means.
|
||||
For such a scenario there is the network bootstrapper tool which in addition to generating the network parameters file
|
||||
also distributes the node info files to the node directories. More information can be found in :doc:`setting-up-a-corda-network`.
|
||||
|
||||
The current set of network parameters:
|
||||
|
||||
:minimumPlatformVersion: The minimum platform version that the nodes must be running. Any node which is below this will
|
||||
not start.
|
||||
:notaries: List of identity and validation type (either validating or non-validating) of the notaries which are permitted
|
||||
in the compatibility zone.
|
||||
:maxMessageSize: Maximum allowed P2P message size sent over the wire in bytes. Any message larger than this will be
|
||||
split up.
|
||||
:maxTransactionSize: Maximum permitted transaction size in bytes.
|
||||
:modifiedTime: The time when the network parameters were last modified by the compatibility zone operator.
|
||||
:epoch: Version number of the network parameters. Starting from 1, this will always increment whenever any of the
|
||||
parameters change.
|
||||
|
||||
.. note:: ``maxTransactionSize`` is currently not enforced in the node, but will be in a later release.
|
||||
|
||||
More parameters may be added in future releases.
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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()
|
||||
}
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -1,13 +1,9 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.readAll
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
@ -69,7 +65,7 @@ class NetworkMapTest {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
||||
.readAll()
|
||||
.deserialize<SignedData<NetworkParameters>>()
|
||||
.deserialize<SignedDataWithCert<NetworkParameters>>()
|
||||
.verified()
|
||||
// We use a random modified time above to make the network parameters unqiue so that we're sure they came
|
||||
// from the server
|
||||
|
@ -64,7 +64,11 @@ class NodeRegistrationTest {
|
||||
|
||||
@Before
|
||||
fun startServer() {
|
||||
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), DEV_ROOT_CA, "localhost", registrationHandler)
|
||||
server = NetworkMapServer(
|
||||
cacheTimeout = 1.minutes,
|
||||
hostAndPort = portAllocation.nextHostAndPort(),
|
||||
myHostNameValue = "localhost",
|
||||
additionalServices = registrationHandler)
|
||||
serverHostAndPort = server.start()
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,15 @@ package net.corda.services.messaging
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
||||
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
|
||||
@ -90,20 +95,13 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
}
|
||||
|
||||
val caKeyStore = loadKeyStore(
|
||||
javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"),
|
||||
"cordacadevpass")
|
||||
|
||||
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val intermediateCA = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
|
||||
val clientKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
// Set name constrain to the legal name.
|
||||
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
|
||||
val clientCACert = X509Utilities.createCertificate(
|
||||
CertificateType.INTERMEDIATE_CA,
|
||||
intermediateCA.certificate,
|
||||
intermediateCA.keyPair,
|
||||
DEV_INTERMEDIATE_CA.certificate,
|
||||
DEV_INTERMEDIATE_CA.keyPair,
|
||||
legalName.x500Principal,
|
||||
clientKeyPair.public,
|
||||
nameConstraints = nameConstraints)
|
||||
@ -123,7 +121,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
X509Utilities.CORDA_CLIENT_CA,
|
||||
clientKeyPair.private,
|
||||
keyPass,
|
||||
arrayOf(clientCACert, intermediateCA.certificate, rootCACert))
|
||||
arrayOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||
clientCAKeystore.save(nodeKeystore, keyStorePassword)
|
||||
|
||||
val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword)
|
||||
@ -131,7 +129,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKeyPair.private,
|
||||
keyPass,
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))
|
||||
arrayOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||
tlsKeystore.save(sslKeystore, keyStorePassword)
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.context.InvocationContext
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
@ -67,6 +66,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
|
||||
import net.corda.nodeapi.internal.network.NetworkParameters
|
||||
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
|
||||
@ -208,7 +208,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||
val identityService = makeIdentityService(identity.certificate)
|
||||
networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) }
|
||||
retrieveNetworkParameters()
|
||||
retrieveNetworkParameters(identityService.trustRoot)
|
||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
|
||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
|
||||
@ -643,23 +643,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
return PersistentKeyManagementService(identityService, keyPairs)
|
||||
}
|
||||
|
||||
private fun retrieveNetworkParameters() {
|
||||
val networkParamsFile = configuration.baseDirectory.list { paths ->
|
||||
paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().orElse(null)
|
||||
}
|
||||
private fun retrieveNetworkParameters(trustRoot: X509Certificate) {
|
||||
val networkParamsFile = configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME
|
||||
|
||||
networkParameters = if (networkParamsFile != null) {
|
||||
networkParamsFile.readAll().deserialize<SignedData<NetworkParameters>>().verified()
|
||||
networkParameters = if (networkParamsFile.exists()) {
|
||||
networkParamsFile.readAll().deserialize<SignedDataWithCert<NetworkParameters>>().verifiedNetworkMapCert(trustRoot)
|
||||
} else {
|
||||
log.info("No network-parameters file found. Expecting network parameters to be available from the network map.")
|
||||
val networkMapClient = checkNotNull(networkMapClient) {
|
||||
"Node hasn't been configured to connect to a network map from which to get the network parameters"
|
||||
}
|
||||
val (networkMap, _) = networkMapClient.getNetworkMap()
|
||||
val signedParams = checkNotNull(networkMapClient.getNetworkParameter(networkMap.networkParameterHash)) {
|
||||
"Failed loading network parameters from network map server"
|
||||
}
|
||||
val verifiedParams = signedParams.verified()
|
||||
val signedParams = networkMapClient.getNetworkParameters(networkMap.networkParameterHash)
|
||||
val verifiedParams = signedParams.verifiedNetworkMapCert(trustRoot)
|
||||
signedParams.serialize().open().copyTo(configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
||||
verifiedParams
|
||||
}
|
||||
|
@ -52,10 +52,7 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
||||
}
|
||||
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
createDevKeyStores(rootCert, intermediateCa, myLegalName)
|
||||
createDevKeyStores(myLegalName)
|
||||
|
||||
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
|
||||
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
|
||||
|
@ -2,28 +2,26 @@ package net.corda.node.services.network
|
||||
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.internal.SignedDataWithCert
|
||||
import net.corda.core.internal.checkOkResponse
|
||||
import net.corda.core.internal.openHttpConnection
|
||||
import net.corda.core.internal.responseAs
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||
import net.corda.node.utilities.NamedThreadFactory
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.network.NetworkMap
|
||||
import net.corda.nodeapi.internal.network.NetworkParameters
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkMap
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Headers
|
||||
import org.apache.commons.io.IOUtils
|
||||
import rx.Subscription
|
||||
import java.io.BufferedReader
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
@ -40,42 +38,29 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509C
|
||||
requestMethod = "POST"
|
||||
setRequestProperty("Content-Type", "application/octet-stream")
|
||||
outputStream.use { signedNodeInfo.serialize().open().copyTo(it) }
|
||||
if (responseCode != 200) {
|
||||
throw IOException("Response Code $responseCode: ${IOUtils.toString(errorStream)}")
|
||||
}
|
||||
checkOkResponse()
|
||||
}
|
||||
}
|
||||
|
||||
fun getNetworkMap(): NetworkMapResponse {
|
||||
val conn = networkMapUrl.openHttpConnection()
|
||||
val signedNetworkMap = conn.inputStream.use { it.readBytes() }.deserialize<SignedNetworkMap>()
|
||||
val networkMap = signedNetworkMap.verified(trustedRoot)
|
||||
val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds
|
||||
val connection = networkMapUrl.openHttpConnection()
|
||||
val signedNetworkMap = connection.responseAs<SignedDataWithCert<NetworkMap>>()
|
||||
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot)
|
||||
val timeout = CacheControl.parse(Headers.of(connection.headerFields.filterKeys { it != null }.mapValues { it.value[0] })).maxAgeSeconds().seconds
|
||||
return NetworkMapResponse(networkMap, timeout)
|
||||
}
|
||||
|
||||
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? {
|
||||
val conn = URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection()
|
||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
null
|
||||
} else {
|
||||
val signedNodeInfo = conn.inputStream.use { it.readBytes() }.deserialize<SignedNodeInfo>()
|
||||
signedNodeInfo.verified()
|
||||
}
|
||||
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo {
|
||||
return URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection().responseAs<SignedNodeInfo>().verified()
|
||||
}
|
||||
|
||||
fun getNetworkParameter(networkParameterHash: SecureHash): SignedData<NetworkParameters>? {
|
||||
val conn = URL("$networkMapUrl/network-parameter/$networkParameterHash").openHttpConnection()
|
||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
null
|
||||
} else {
|
||||
conn.inputStream.use { it.readBytes() }.deserialize()
|
||||
}
|
||||
fun getNetworkParameters(networkParameterHash: SecureHash): SignedDataWithCert<NetworkParameters> {
|
||||
return URL("$networkMapUrl/network-parameters/$networkParameterHash").openHttpConnection().responseAs()
|
||||
}
|
||||
|
||||
fun myPublicHostname(): String {
|
||||
val conn = URL("$networkMapUrl/my-hostname").openHttpConnection()
|
||||
return conn.inputStream.bufferedReader().use(BufferedReader::readLine)
|
||||
val connection = URL("$networkMapUrl/my-hostname").openHttpConnection()
|
||||
return connection.inputStream.bufferedReader().use(BufferedReader::readLine)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,9 +24,7 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr
|
||||
@Throws(CertificateRequestException::class)
|
||||
override fun retrieveCertificates(requestId: String): Array<Certificate>? {
|
||||
// Poll server to download the signed certificate once request has been approved.
|
||||
val url = URL("$registrationURL/$requestId")
|
||||
|
||||
val conn = url.openConnection() as HttpURLConnection
|
||||
val conn = URL("$registrationURL/$requestId").openHttpConnection()
|
||||
conn.requestMethod = "GET"
|
||||
|
||||
return when (conn.responseCode) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.seconds
|
||||
@ -22,7 +21,6 @@ import org.junit.Test
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class NetworkMapClientTest {
|
||||
@Rule
|
||||
@ -83,8 +81,8 @@ class NetworkMapClientTest {
|
||||
@Test
|
||||
fun `download NetworkParameter correctly`() {
|
||||
// The test server returns same network parameter for any hash.
|
||||
val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())?.verified()
|
||||
assertNotNull(networkParameter)
|
||||
val parametersHash = server.networkParameters.serialize().hash
|
||||
val networkParameter = networkMapClient.getNetworkParameters(parametersHash).verified()
|
||||
assertEquals(server.networkParameters, networkParameter)
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,16 @@
|
||||
package net.corda.testing.node.internal.network
|
||||
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.signWithCert
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.network.DigitalSignatureWithCert
|
||||
import net.corda.nodeapi.internal.network.NetworkMap
|
||||
import net.corda.nodeapi.internal.network.NetworkParameters
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkMap
|
||||
import net.corda.testing.DEV_ROOT_CA
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
@ -28,34 +24,19 @@ import java.net.InetSocketAddress
|
||||
import java.security.SignatureException
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
import javax.ws.rs.core.Response.ok
|
||||
import javax.ws.rs.core.Response.status
|
||||
|
||||
class NetworkMapServer(cacheTimeout: Duration,
|
||||
class NetworkMapServer(private val cacheTimeout: Duration,
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
rootCa: CertificateAndKeyPair = DEV_ROOT_CA,
|
||||
private val networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(),
|
||||
private val myHostNameValue: String = "test.host.name",
|
||||
vararg additionalServices: Any) : Closeable {
|
||||
companion object {
|
||||
private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, 40000, Instant.now(), 10)
|
||||
|
||||
private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair {
|
||||
val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val networkMapCert = X509Utilities.createCertificate(
|
||||
CertificateType.NETWORK_MAP,
|
||||
rootCAKeyAndCert.certificate,
|
||||
rootCAKeyAndCert.keyPair,
|
||||
X500Principal("CN=Corda Network Map,O=R3 Ltd,L=London,C=GB"),
|
||||
networkMapKey.public)
|
||||
// Check that the certificate validates. Nodes will perform this check upon receiving a network map,
|
||||
// it's better to fail here than there.
|
||||
X509Utilities.validateCertificateChain(rootCAKeyAndCert.certificate, networkMapCert)
|
||||
return CertificateAndKeyPair(networkMapCert, networkMapKey)
|
||||
}
|
||||
}
|
||||
|
||||
private val server: Server
|
||||
@ -64,9 +45,7 @@ class NetworkMapServer(cacheTimeout: Duration,
|
||||
check(field == stubNetworkParameters) { "Network parameters can be set only once" }
|
||||
field = networkParameters
|
||||
}
|
||||
private val serializedParameters get() = networkParameters.serialize()
|
||||
private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(rootCa))
|
||||
|
||||
private val service = InMemoryNetworkMapService()
|
||||
|
||||
init {
|
||||
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
|
||||
@ -106,14 +85,10 @@ class NetworkMapServer(cacheTimeout: Duration,
|
||||
}
|
||||
|
||||
@Path("network-map")
|
||||
inner class InMemoryNetworkMapService(private val cacheTimeout: Duration,
|
||||
private val networkMapKeyAndCert: CertificateAndKeyPair) {
|
||||
inner class InMemoryNetworkMapService {
|
||||
private val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>()
|
||||
private val parametersHash by lazy { serializedParameters.hash }
|
||||
private val signedParameters by lazy {
|
||||
SignedData(
|
||||
serializedParameters,
|
||||
DigitalSignature.WithKey(networkMapKeyAndCert.keyPair.public, Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedParameters.bytes)))
|
||||
private val signedNetParams by lazy {
|
||||
networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
|
||||
}
|
||||
|
||||
@POST
|
||||
@ -121,10 +96,9 @@ class NetworkMapServer(cacheTimeout: Duration,
|
||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun publishNodeInfo(input: InputStream): Response {
|
||||
return try {
|
||||
val registrationData = input.readBytes().deserialize<SignedNodeInfo>()
|
||||
val nodeInfo = registrationData.verified()
|
||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||
nodeInfoMap.put(nodeInfoHash, registrationData)
|
||||
val signedNodeInfo = input.readBytes().deserialize<SignedNodeInfo>()
|
||||
signedNodeInfo.verified()
|
||||
nodeInfoMap[signedNodeInfo.raw.hash] = signedNodeInfo
|
||||
ok()
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
@ -137,10 +111,8 @@ class NetworkMapServer(cacheTimeout: Duration,
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun getNetworkMap(): Response {
|
||||
val networkMap = NetworkMap(nodeInfoMap.keys.toList(), parametersHash)
|
||||
val serializedNetworkMap = networkMap.serialize()
|
||||
val signature = Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedNetworkMap.bytes)
|
||||
val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate, signature))
|
||||
val networkMap = NetworkMap(nodeInfoMap.keys.toList(), signedNetParams.raw.hash)
|
||||
val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
|
||||
return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${cacheTimeout.seconds}").build()
|
||||
}
|
||||
|
||||
@ -162,16 +134,15 @@ class NetworkMapServer(cacheTimeout: Duration,
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("network-parameter/{var}")
|
||||
@Path("network-parameters/{var}")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun getNetworkParameter(@PathParam("var") networkParameterHash: String): Response {
|
||||
return Response.ok(signedParameters.serialize().bytes).build()
|
||||
fun getNetworkParameter(@PathParam("var") hash: String): Response {
|
||||
require(signedNetParams.raw.hash == SecureHash.parse(hash))
|
||||
return Response.ok(signedNetParams.serialize().bytes).build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("my-hostname")
|
||||
fun getHostName(): Response {
|
||||
return Response.ok(myHostNameValue).build()
|
||||
}
|
||||
fun getHostName(): Response = Response.ok(myHostNameValue).build()
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,6 @@ import net.corda.core.contracts.TypeOnlyCommandData
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.getCertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
|
||||
@ -32,17 +29,10 @@ val ALICE_NAME = CordaX500Name("Alice Corp", "Madrid", "ES")
|
||||
val BOB_NAME = CordaX500Name("Bob Plc", "Rome", "IT")
|
||||
@JvmField
|
||||
val CHARLIE_NAME = CordaX500Name("Charlie Ltd", "Athens", "GR")
|
||||
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair by lazy {
|
||||
// TODO: Should be identity scheme
|
||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
}
|
||||
|
||||
val DEV_ROOT_CA: CertificateAndKeyPair by lazy {
|
||||
// TODO: Should be identity scheme
|
||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, "cordacadevkeypass")
|
||||
}
|
||||
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA }
|
||||
|
||||
val DEV_ROOT_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_ROOT_CA }
|
||||
|
||||
fun dummyCommand(vararg signers: PublicKey = arrayOf(generateKeyPair().public)) = Command<TypeOnlyCommandData>(DummyCommandData, signers.toList())
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user