diff --git a/.gitignore b/.gitignore index 05182bed10..f267b20469 100644 --- a/.gitignore +++ b/.gitignore @@ -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. diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index 1657eb2d4a..ef365c7a4f 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -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]. diff --git a/core/src/main/kotlin/net/corda/core/internal/DigitalSignatureWithCert.kt b/core/src/main/kotlin/net/corda/core/internal/DigitalSignatureWithCert.kt new file mode 100644 index 0000000000..04ca25c6cb --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/DigitalSignatureWithCert.kt @@ -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(val raw: SerializedBytes, val sig: DigitalSignatureWithCert) { + fun verified(): T { + sig.verify(raw) + return uncheckedCast(raw.deserialize()) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 1ad9e16630..70724cb884 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -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 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.signWithCert(privateKey: PrivateKey, certificate: X509Certificate): SignedDataWithCert { + val serialised = serialize() + val signature = Crypto.doSign(privateKey, serialised.bytes) + return SignedDataWithCert(serialised, DigitalSignatureWithCert(certificate, signature)) +} diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst index efe8110843..f6eca336f4 100644 --- a/docs/source/network-map.rst +++ b/docs/source/network-map.rst @@ -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, - 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`` 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. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt index 0cb8472f89..9ccc286f4f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -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") } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt index 374a4cdf24..2ba6192454 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt @@ -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") + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt index 1b0ed15348..ea7de12aa2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt @@ -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, val signatures: List) { + // 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 } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt index f784a4297b..118683f753 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt @@ -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, 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 SignedDataWithCert.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() +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt index fe9b88a24e..fdcb28fef2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt @@ -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. } } -} \ No newline at end of file +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index af38597bfe..05ec9db2e5 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -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) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index 5530ba7850..67b5d35a76 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -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>() + .deserialize>() .verified() // We use a random modified time above to make the network parameters unqiue so that we're sure they came // from the server diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index 17544c8bd0..7b77464ed9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -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() } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index 8252173ca0..e8aef9bffd 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -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) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 0edc5a1624..0f988ff0a1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -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>().verified() + networkParameters = if (networkParamsFile.exists()) { + networkParamsFile.readAll().deserialize>().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 } diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index f48e8d4022..be5428675a 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -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" diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index 5064361c7b..4e1e2c821c 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -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() - 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>() + 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.verified() - } + fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo { + return URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection().responseAs().verified() } - fun getNetworkParameter(networkParameterHash: SecureHash): SignedData? { - 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 { + 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) } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt index 88d9230a61..49dde51b6d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt @@ -24,9 +24,7 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr @Throws(CertificateRequestException::class) override fun retrieveCertificates(requestId: String): Array? { // 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) { diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index fa0a67fe15..da4784d060 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -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) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index 810fcedf77..52975bbdb3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -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() - 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() - val nodeInfo = registrationData.verified() - val nodeInfoHash = nodeInfo.serialize().sha256() - nodeInfoMap.put(nodeInfoHash, registrationData) + val signedNodeInfo = input.readBytes().deserialize() + 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() } } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index 406477c52b..17fb7e1294 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -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(DummyCommandData, signers.toList())