remove network services from corda repo (#875)

network services is in : https://github.com/corda/network-services
This commit is contained in:
Patrick Kuo 2018-05-24 18:07:23 +01:00 committed by GitHub
parent 77ef131c0f
commit 9155000407
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
164 changed files with 0 additions and 18685 deletions

View File

@ -1,374 +0,0 @@
# Building the binaries
## Network management server
To build a fat jar containing all the doorman code you can simply invoke:
```
./gradlew network-management:capsule:buildDoormanJAR
```
The built file will appear in:
```
network-management/capsule/build/libs/doorman-<version>.jar
```
## HSM signing server
To build a fat jar containing all the HSM signing server code you can simply invoke:
```
./gradlew network-management:capsule-hsm:buildHsmJAR
```
The built file will appear in:
```
network-management/capsule-hsm/build/libs/hsm-<version>.jar
```
The binaries can also be obtained from artifactory after deployment in TeamCity.
To run the HSM signing server:
```
cd network-management
java -jar capsule-hsm/build/libs/hsm-<version>.jar --config-file [hsm-configuration-file]
```
For a list of options the HSM signing server takes, run with the `--help` option:
```
java -jar capsule-hsm/build/libs/hsm-<version>.jar --help
```
## HSM Certificate Generator
To build a fat jar containing all the hsm certificate generator code you can simply invoke
```
./gradlew network-management:capsule-hsm-cert-generator:buildHsmCertGeneratorJAR
```
The built file will appear in
```
network-management/capsule-hsm-cert-generator/build/libs/hsm-cert-generator-<VERSION>.jar
```
## HSM CRL Generator
To build a fat jar containing all the hsm CRL generator code you can simply invoke
```
./gradlew network-management:capsule-hsm-crl-generator:buildHsmCrlGeneratorJAR
```
The built file will appear in
```
network-management/capsule-hsm-crl-generator/build/libs/hsm-crl-generator-<VERSION>.jar
```
## Certificate Revocation Request Submission Tool
To build a fat jar containing all the CRR submission tool code you can simply invoke
```
./gradlew network-management:capsule-crr-submission:buildCrrSubmissionJAR
```
The built file will appear in
```
network-management/capsule-crr-submission/build/libs/crr-submission-<VERSION>.jar
```
# Logs
In order to set the desired logging level the system properties need to be used.
Appropriate system properties can be set at the execution time.
Example:
```
java -DdefaultLogLevel=TRACE -DconsoleLogLevel=TRACE -jar doorman-<version>.jar --config-file <config file>
```
#Configuring network management service
### Local signing
When `keystorePath` is provided in the config file, a signer will be created to handle all the signing periodically
using the CA keys in the provided keystore.
The network management service can be started without a signer, the signing will be delegated to external process
(e.g. HSM) connecting to the same database, the server will poll the database periodically for newly signed data and
update the statuses accordingly.
Additional configuration needed for local signer:
```
#For local signing
rootStorePath = ${basedir}"/certificates/rootstore.jks"
keystorePath = ${basedir}"/certificates/caKeystore.jks"
keystorePassword = "password"
caPrivateKeyPassword = "password"
```
## Doorman Service
Doorman service can be started with the following options :
### JIRA
The doorman service can use JIRA to manage both the certificate signing request and the certificate revocation request approval work flows.
This can be turned on by providing JIRA connection configuration in the config file.
```
doorman {
jira {
address = "https://doorman-jira-host.com/"
projectCode = "TD"
username = "username"
password = "password"
}
.
.
.
}
```
#### JIRA project configuration
* The JIRA project should setup as "Business Project" with "Task" workflow.
* Custom text field input "Request ID", and "Reject Reason" should be created in JIRA, doorman will exit with error without
these custom fields.
### Auto approval
When `approveAll` is set to `true`, the doorman will approve all requests on receive. (*This should only be enabled in a
test environment)
### Network map service
Network map service can be enabled by providing the following config:
```
networkMap {
cacheTimeout = 600000
signInterval = 10000
}
```
`cacheTimeout`(ms) determines how often the server should poll the database for a newly signed network map and also how often nodes should poll for a new network map (by including this value in the HTTP response header). **This is not how often changes to the network map are signed, which is a different process.**
`signInterval`(ms) this is only relevant when local signer is enabled. The signer poll the database according to the `signInterval`, and create a new network map if the collection of node info hashes is different from the current network map.
## Example config file
```
basedir = "."
address = "localhost:0"
rootStorePath = ${basedir}"/certificates/rootstore.jks"
keystorePath = ${basedir}"/certificates/caKeystore.jks"
#keystorePassword = "password" #Optional if not specified, user will be prompted on the console.
#caPrivateKeyPassword = "password" #Optional if not specified, user will be prompted on the console.
#rootPrivateKeyPassword = "password" #Optional if not specified, user will be prompted on the console.
#rootKeystorePassword = "password" #Optional if not specified, user will be prompted on the console.
dataSourceProperties {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
"dataSource.user" = sa
"dataSource.password" = ""
}
database {
runMigration = true
}
h2port = 0
# Comment out this section if running without doorman service
doorman {
approveInterval = 10000
approveAll = false
jira {
address = "https://doorman-jira-host.com/"
projectCode = "TD"
username = "username"
password = "password"
}
}
# Comment out this section if running without the revocation service
revocation {
approveInterval = 10000
approveAll = false
crlUpdateInterval = 86400000
crlEndpoint = "http://test.com/crl"
crlCacheTimeout = 86400000
jira {
address = "https://doorman-jira-host.com/"
projectCode = "CRR"
username = "username"
password = "password"
}
}
# Comment out this section if running without network map service
networkMap {
cacheTimeout = 600000
signInterval = 10000
}
```
# Running the network
### 1. Create keystore for local signer
If local signer is enabled, the server will look for key stores in the certificate folder on start up.
The key stores can be created using `--mode` flag.
```
java -jar doorman-<version>.jar --config-file <config file> --mode ROOT_KEYGEN
```
and
```
java -jar doorman-<version>.jar --config-file <config file> --mode CA_KEYGEN
```
A trust store containing the root certificate will be created in the location `distribute-nodes / network-root-truststore.jks`
(relative to `rootStorePath`). The trust store's password can be set using command line argument `--trust-store-password`,
or the doorman's keygen utility will ask for password input if trust store password is not provided using this flag.
This trust store file is to be distributed to every node that wishes to register with the doorman. The node cannot
register without it.
### 2. Start Doorman service for notary registration
Start the network management server with the doorman service for initial bootstrapping. Network map service (`networkMap`)
should be **disabled** at this point. **Comment out** network map config in the config file and start the server by running :
```
java -jar doorman-<version>.jar --config-file <config file>
```
### 3. Create notary node and register with the doorman
After the doorman service is started, start the notary node for registration.
```
java -jar corda.jar --initial-registration --network-root-truststore-password <trust store password>
```
By default it will expect trust store file received from the doorman to be in the location ``certificates/network-root-truststore.jks``.
This can be overridden with the additional `--network-root-truststore` flag.
NOTE: This step applies to all nodes that wish to register with the doorman. You will have to configure ``compatibiityZoneURL`` and set ``devMode`` to false on each node.
### 4. Generate node info files for notary nodes
Once notary nodes are registered, run the notary nodes with the `just-generate-node-info` flag.
This will generate the node info files, which then should be referenced in the network parameters configuration.
### 5. Add notary identities to the network parameters
The network parameters should contain reference to the notaries node info files.
Example network parameters file:
notaries : [
{
notaryNodeInfoFile: "/Path/To/NodeInfo/File1"
validating: true
},
{
notaryNodeInfoFile: "/Path/To/NodeInfo/File2"
validating: false
}
]
minimumPlatformVersion = 1
maxMessageSize = 10485760
maxTransactionSize = 10485760
eventHorizonDays = 30 # Duration in days
Save the parameters to `network-parameters.conf`
### 6. Load the initial network parameters
A network parameters file is required to start the network map service for the first time. The initial network parameters
file can be loaded using the `--set-network-parameters` flag. We can now restart the network management server with
both doorman and network map service.
```
java -jar doorman-<version>.jar --config-file <config file> --set-network-parameters network-parameters.conf
```
The server will terminate once this process is complete. Start it back up again with both the doorman and network map service.
```
java -jar doorman-<version>.jar --config-file <config file>
```
### 7. Archive policy
The ``node_info`` and ``network_map`` table are designed to retain all historical data for auditing purposes and will grow over time.
**It is recommended to monitor the space usage and archive these tables according to the data retention policy.**
Run the following SQL script to archive the node info table (change the timestamp according to the archive policy):
```
delect from node_info where is_current = false and published_at < '2018-03-12'
```
# Updating the network parameters
The initial network parameters can be subsequently changed through an update process. However, these changes must first
be advertised to the entire network to allow nodes time to agree to the changes. Every time the server needs to be shutdown
and run with one of the following flags: `--set-network-parameters`, `--flag-day` or `--cancel-update`. For change to be
advertised to the nodes new network map has to be signed (either by HSM or by local signer).
Typical update process is as follows:
1. Start network map with initial network parameters.
2. To advertise an update:
* Stop network-management.
* Run it with ``--set-network-parameters`` flag. The network parameters file must have `parametersUpdate` config block:
```
parametersUpdate {
description = "Important update"
updateDeadline = "2017-08-31T05:10:36.297Z" # ISO-8601 time, substitute it with update deadline
}
```
Where `description` is a short description of the update that will be communicated to the nodes and `updateDeadline` is
the time (in ISO-8601 format) by which all nodes in the network must decide that they have accepted the new parameters.
NOTE: Currently only backwards compatible changes to the network parameters can be made, i.e. notaries can't be removed,
max transaction size can only increase, etc.
The process will exit, nothing will be sent to the nodes yet.
* Start network-management as normal without any flags. This time, the nodes will be notified of the new parameters
update next time they poll.
3. Before the `updateDeadline` time, nodes will have to run the RPC command to accept new parameters.
This will not activate the new network parameters on the nodes. It is possible to poll the network map database to check
how many network participants have accepted the new network parameters - the information is stored in the `node-info.accepted_parameters_hash` column.
4. When the flag day comes. Restart network-management with ``--flag-day`` flag. This will cause all nodes in the network
to shutdown when they see that the network parameters have changed.
The nodes that didn't accept the parameters will be removed from the network map. The ones that accepted, will need to be manually restarted.
It is possible to cancel the previously scheduled update. To do so simply run:
```
java -jar doorman-<version>.jar --cancel-update
```
The network map will continue to advertise the cancelled update until the new network map is signed.
# Private Network Map
The private network is a tactical solution to provide temporary privacy to the initial network map.
## Creating a private network
To create a new private network, an entry has to be created in the ``private_network`` table manually.
Run the following SQL script to create a new private network:
```
insert into private_network (id, name)
values (NEWID(), 'Private Network Name')
```
Then use the following SQL to retrieve the private network ID for the private network owner:
```
select id from private_network where name = 'Private Network Name'
```
## Modify existing private network registration
Since this is a tactical solution, any modification will require manual database changes.
**We should try to keep these changes to the minimal**
### Add nodes to a private network
```
update certificate_signing_request
set private_network = '<<private_network_id>>'
where request_id in ('<<certificate_request_id>>', ...)
```
or this SQL script to add all approved nodes to the private network map.
```
update certificate_signing_request
set private_network = '<<private_network_id>>'
where status = 'APPROVED'
```
**Important**
If notary is to be used by private network participants add private network UUIDs to notary's ``node.conf`` using
``extraNetworkMapKeys`` list.
### Move a node from its private network and into the global network map**
```
update certificate_signing_request
set private_network = null
where request_id = '<<certificate_request_id>>'
```

View File

@ -1,124 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
description 'Network management module encapsulating components such as Doorman, HSM Signing Service and Network Map'
apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'kotlin'
apply plugin: 'kotlin-jpa'
repositories {
mavenLocal()
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
jcenter()
maven {
url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-dev'
}
maven {
url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-releases'
}
}
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
sourceSets {
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/kotlin')
}
java {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/java')
}
resources {
srcDir file('src/integration-test/resources')
}
}
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
}
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile project(':node-api')
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
// Web stuff: for HTTP[S] servlets
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
compile "javax.servlet:javax.servlet-api:3.1.0"
// Jersey for JAX-RS implementation for use in Jetty
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
// JOpt: for command line flags.
compile "net.sf.jopt-simple:jopt-simple:5.0.4"
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
// Hibernate audit plugin
compile "org.hibernate:hibernate-envers:5.2.11.Final"
// Manifests: for reading stuff from the manifest file
compile "com.jcabi:jcabi-manifests:1.1"
testCompile project(':test-utils')
testCompile project(':node-driver')
// Unit testing helpers.
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
testCompile "com.nhaarman:mockito-kotlin:1.5.0"
testCompile "com.spotify:docker-client:8.9.1"
compile('com.atlassian.jira:jira-rest-java-client-core:5.0.4') {
// The jira client includes jersey-core 1.5 which breaks everything.
exclude module: 'jersey-core'
}
// Needed by jira rest client
compile "com.atlassian.fugue:fugue:2.6.1"
// SQL connection pooling library
compile "com.zaxxer:HikariCP:${hikari_version}"
// For H2 database support in persistence
compile "com.h2database:h2:$h2_version"
//TODO remove once we can put driver jar into a predefined directory
//JDBC driver can be passed to the Node at startup using setting the jarDirs property in the Node configuration file.
compile 'com.microsoft.sqlserver:mssql-jdbc:6.2.1.jre8'
// Bouncy Castle for HSM signing
compile "org.bouncycastle:bcprov-jdk15on:${bouncycastle_version}"
compile "org.bouncycastle:bcpkix-jdk15on:${bouncycastle_version}"
}

View File

@ -1,49 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'us.kirchmeier.capsule'
description 'HSM Certificate Generator'
version project(':network-management').version
configurations {
runtimeArtifacts.extendsFrom runtime
}
task buildCrrSubmissionJAR(type: FatCapsule, dependsOn: 'jar') {
applicationClass 'com.r3.corda.networkmanage.tools.crr.submission.MainKt'
archiveName "crr-submission-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'CRR Submission Tool'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
applicationSource = files(
project(':network-management').configurations.runtime,
project(':network-management').jar
)
}
artifacts {
runtimeArtifacts buildCrrSubmissionJAR
publish buildCrrSubmissionJAR
}
jar {
classifier "ignore"
}
publish {
name 'crr-submission'
disableDefaultJar = true
}

View File

@ -1,49 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'us.kirchmeier.capsule'
description 'HSM Certificate Generator'
version project(':network-management').version
configurations {
runtimeArtifacts.extendsFrom runtime
}
task buildHsmCertGeneratorJAR(type: FatCapsule, dependsOn: 'jar') {
applicationClass 'com.r3.corda.networkmanage.hsm.generator.certificate.MainKt'
archiveName "hsm-cert-generator-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'HSM Certificate Generator'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
applicationSource = files(
project(':network-management').configurations.runtime,
project(':network-management').jar
)
}
artifacts {
runtimeArtifacts buildHsmCertGeneratorJAR
publish buildHsmCertGeneratorJAR
}
jar {
classifier "ignore"
}
publish {
name 'hsm-cert-generator'
disableDefaultJar = true
}

View File

@ -1,49 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'us.kirchmeier.capsule'
description 'HSM Certificate Generator'
version project(':network-management').version
configurations {
runtimeArtifacts.extendsFrom runtime
}
task buildHsmCrlGeneratorJAR(type: FatCapsule, dependsOn: 'jar') {
applicationClass 'com.r3.corda.networkmanage.hsm.generator.crl.MainKt'
archiveName "hsm-crl-generator-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'HSM CRL Generator'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
applicationSource = files(
project(':network-management').configurations.runtime,
project(':network-management').jar
)
}
artifacts {
runtimeArtifacts buildHsmCrlGeneratorJAR
publish buildHsmCrlGeneratorJAR
}
jar {
classifier "ignore"
}
publish {
name 'hsm-crl-generator'
disableDefaultJar = true
}

View File

@ -1,55 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'com.jfrog.artifactory'
description 'Doorman default'
version project(':network-management').version
configurations {
runtimeArtifacts.extendsFrom runtime
}
task buildHsmJAR(type: FatCapsule, dependsOn: 'jar') {
applicationClass 'com.r3.corda.networkmanage.hsm.MainKt'
archiveName "hsm-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'HSM Signing Service'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
applicationSource = files(
project(':network-management').configurations.runtime,
project(':network-management').jar
)
tasks.withType(Jar) { task ->
manifest {
attributes('Signing-Service-Version': version)
}
}
}
artifacts {
runtimeArtifacts buildHsmJAR
publish buildHsmJAR
}
jar {
classifier "ignore"
}
publish {
name 'doorman-hsm'
disableDefaultJar = true
}

View File

@ -1,57 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'com.jfrog.artifactory'
description 'Doorman default'
version project(':network-management').version
configurations {
runtimeArtifacts.extendsFrom runtime
}
task buildDoormanJAR(type: FatCapsule, dependsOn: ':network-management:jar') {
applicationClass 'com.r3.corda.networkmanage.doorman.MainKt'
archiveName "doorman-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'Doorman'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
applicationSource = files(
project(':network-management').configurations.runtime,
project(':network-management').jar
)
tasks.withType(Jar) { task ->
manifest {
attributes('Doorman-Version': version)
}
}
}
artifacts {
runtimeArtifacts buildDoormanJAR
publish buildDoormanJAR {
classifier ""
}
}
jar {
classifier "ignore"
}
publish {
name 'doorman'
disableDefaultJar = true
}

View File

@ -1,28 +0,0 @@
hsmHost = 127.0.0.1
hsmPort = 3001
trustStoreDirectory = "."
trustStorePassword = "trustpass"
certConfig {
subject = "CN=Corda Root, O=R3 HoldCo LLC, OU=Corda, L=New York, C=US"
certificateType = ROOT_CA
validDays = 3650
keyOverride = 0
keyAlgorithm = 4
keyExport = 0
keyCurve = "NIST-P256"
keyGenMechanism = 4
keyGroup = "DEV.DOORMAN"
keySpecifier = 1
storeKeysExternal = false
crlDistributionUrl = "http://r3.com/revoked.crl"
crlIssuer = "CN=Corda Test, O=R3Cev, L=London, C=GB"
}
userConfigs = [
{
username = "INTEGRATION_TEST"
authMode = PASSWORD
authToken = "INTEGRATION_TEST"
}
]

View File

@ -1,26 +0,0 @@
basedir = "."
host = localhost
port = 0
# Database config
dataSourceProperties {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
"dataSource.user" = sa
"dataSource.password" = ""
}
h2port = 0
# Doorman config
# Comment out this section if running without doorman service
doorman {
approveInterval = 10000
approveAll = false
}
# Network map config
# Comment out this section if running without network map service
networkMap {
cacheTimeout = 600000
signInterval = 10000
}

View File

@ -1,33 +0,0 @@
basedir = "."
host = localhost
port = 0
# Database config
dataSourceProperties {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
"dataSource.user" = sa
"dataSource.password" = ""
}
h2port = 0
# Doorman config
# Comment out this section if running without doorman service
doorman {
approveInterval = 10000
approveAll = false
jira {
address = "https://doorman-jira-host.com/"
projectCode = "TD"
username = "username"
password = "password"
doneTransitionCode = 41
}
}
# Network map config
# Comment out this section if running without network map service
networkMap {
cacheTimeout = 600000
signInterval = 10000
}

View File

@ -1,33 +0,0 @@
basedir = "."
host = localhost
port = 0
#For local signing
rootStorePath = ${basedir}"/certificates/rootstore.jks"
keystorePath = ${basedir}"/certificates/caKeystore.jks"
keystorePassword = "password"
caPrivateKeyPassword = "password"
# Database config
dataSourceProperties {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
"dataSource.user" = sa
"dataSource.password" = ""
}
h2port = 0
# Doorman config
# Comment out this section if running without doorman service
doorman {
approveInterval = 10000
approveAll = false
}
# Network map config
# Comment out this section if running without network map service
networkMap {
cacheTimeout = 600000
signInterval = 10000
}

View File

@ -1,33 +0,0 @@
hsmHost = 127.0.0.1
hsmPort = 3001
trustStoreFile = "./truststore.jks"
trustStorePassword = "trustpass"
crl {
keyGroup = "TEST.CORDACONNECT.ROOT"
keySpecifier = 1
validDays = 3650
crlEndpoint = "http://test.com/crl"
indirectIssuer = true
filePath = "./bytes.crl"
revocations = [
{
certificateSerialNumber = "12345"
dateInMillis = 1526643707290
reason = "KEY_COMPROMISE"
},
{
certificateSerialNumber = "6789012"
dateInMillis = 1526643712345
reason = "KEY_COMPROMISE"
}
]
}
userConfigs = [
{
username = "INTEGRATION_TEST"
authMode = PASSWORD
authToken = "INTEGRATION_TEST"
}
]

View File

@ -1,6 +0,0 @@
privateKeyPass ="cordacadevkeypass"
keyStorePass = "cordacadevpass"
keyStoreFileName = "cordadevcakeys.jks"
trustStorePass = "trustpass"
trustStoreFileName = "cordatruststore.jks"
directory = "./certificates"

View File

@ -1,55 +0,0 @@
basedir = "."
address = "localhost:0"
rootStorePath = ${basedir}"/certificates/rootstore.jks"
keystorePath = ${basedir}"/certificates/caKeystore.jks"
#keystorePassword = "password" #Optional if not specified, user will be prompted on the console.
#caPrivateKeyPassword = "password" #Optional if not specified, user will be prompted on the console.
#rootPrivateKeyPassword = "password" #Optional if not specified, user will be prompted on the console.
#rootKeystorePassword = "password" #Optional if not specified, user will be prompted on the console.
dataSourceProperties {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
"dataSource.user" = sa
"dataSource.password" = ""
}
database {
runMigration = true
}
h2port = 0
# Comment out this section if running without the doorman service
doorman {
approveInterval = 10000
approveAll = false
jira {
address = "https://doorman-jira-host.com/"
projectCode = "CSR"
username = "username"
password = "password"
}
}
# Comment out this section if running without the revocation service
revocation {
approveInterval = 10000
approveAll = false
crlUpdateInterval = 86400000
crlEndpoint = "http://test.com/crl"
crlCacheTimeout = 86400000
jira {
address = "https://doorman-jira-host.com/"
projectCode = "CRR"
username = "username"
password = "password"
}
}
# Comment out this section if running without network map service
networkMap {
cacheTimeout = 600000
signInterval = 10000
}

View File

@ -1,27 +0,0 @@
basedir = "."
device = "3001@192.168.0.1"
keySpecifier = -1
doorman {
crlDistributionPoint = "http://test.com/revoked.crl"
crlServerSocketAddress = "test.com:2333"
crlUpdatePeriod = 200000
validDays = 3650
mode = CSR
rootKeyStoreFile = "dummyfile.jks"
rootKeyStorePassword = "trustpass"
keyGroup = "DEV.CORDACONNECT.OPS.CERT"
authParameters {
mode = PASSWORD
password = "PASSWORD"
threshold = 2
}
}
h2port = 0
dataSourceProperties {
"dataSourceClassName" = org.h2.jdbcx.JdbcDataSource
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
"dataSource.user" = sa
"dataSource.password" = ""
}

View File

@ -1,22 +0,0 @@
basedir = "."
device = "3001@192.168.0.1"
keySpecifier = -1
networkMap {
username = "TEST_USERNAME",
keyGroup = "DEV.CORDACONNECT.OPS.NETMAP"
authParameters {
mode = KEY_FILE
password = "PASSWORD"
keyFilePath = "./Administrator.KEY"
threshold = 2
}
}
h2port = 0
dataSourceProperties {
"dataSourceClassName" = org.h2.jdbcx.JdbcDataSource
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
"dataSource.user" = sa
"dataSource.password" = ""
}

View File

@ -1,11 +0,0 @@
notaries : [{
notaryNodeInfoFile: "/Path/To/NodeInfo/File1"
validating: true
}, {
notaryNodeInfoFile: "/Path/To/NodeInfo/File2"
validating: false
}]
minimumPlatformVersion = 1
maxMessageSize = 10485760
maxTransactionSize = 10485760
eventHorizonDays = 30 # Duration in days

View File

@ -1,79 +0,0 @@
#Distributed Notary Registration Tool
The notary registration tool creates a CSR (Certificate Signing Request) with ``SERVICE_IDENTITY`` certificate role and sent to compatibility zone doorman for approval.
A keystore and a trust store will be created once the request is approved.
##Configuration file
The tool creates the CSR using information provided by the config file, the path to the config file should be provided
using the ``--config-file`` flag on start up.
The config file should contain the following parameters.
```
Parameter Description
--------- -----------
legalName Legal name of the requester. It can be in form of X.500 string or CordaX500Name in typesafe config object format.
email Requester's e-mail address.
compatibilityZoneURL Compatibility zone URL.
networkRootTrustStorePath Path to the network root trust store.
networkRootTrustStorePassword Network root trust store password, to be provided by the network operator. Optional, the tool will prompt for password input if not provided.
keyStorePassword Generated keystore's password. Optional, the tool will prompt for password input if not provided.
trustStorePassword Generated trust store's password. Optional, the tool will prompt for password input if not provided.
keystorePath [Optional] Path of the generated keystore file, default to certificates/notaryidentitykeystore.jks
```
Example config file
```
legalName {
organisationUnit = "R3 Corda"
organisation = "R3 LTD"
locality = "London"
country = "GB"
}
# legalName = "C=GB, L=London, O=R3 LTD, OU=R3 Corda"
email = "test@email.com"
compatibilityZoneURL = "http://doorman.url.com"
networkRootTrustStorePath = "networkRootTrustStore.jks"
networkRootTrustStorePassword = "password"
keyStorePassword = "password"
trustStorePassword = "password"
```
##Running the registration tool
``java -jar registration-tool-<<version>>.jar --config-file <<config file path>>``
# Private key copy tool
The key copy tool copies the private key from the source keystore to the destination keystore, it's similar to the
``importkeystore`` option in Java keytool with extra support for Corda's key algorithms.
**This is useful for provisioning keystores for distributed notaries.**
### Usage
``java -jar registration-tool-<<version>>.jar --importkeystore [options]``
### Example
Copy ``distributed-notary-private-key`` from distributed notaries keystore to node's keystore.
```
java -jar registration-tool-<<version>>.jar \
--importkeystore \
--srckeystore notaryKeystore.jks \
--destkeystore node.jks \
--srcstorepass notaryPassword \
--deststorepass nodepassword \
--srcalias distributed-notary-private-key
```
``--help`` prints a list of all the available options.

View File

@ -1,75 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'kotlin'
description 'Network registration tool'
version project(':network-management').version
repositories {
mavenLocal()
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
jcenter()
maven {
url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-dev'
}
maven {
url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-releases'
}
}
configurations {
runtimeArtifacts.extendsFrom runtime
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
task buildRegistrationTool(type: FatCapsule, dependsOn: 'jar') {
group = "build"
applicationClass 'com.r3.corda.networkmanage.registration.MainKt'
archiveName "registration-tool-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'Corda Network Registration Tool'
minJavaVersion = '1.8.0'
}
applicationSource = files(
project(':network-management:registration-tool').configurations.runtime,
project(':network-management:registration-tool').jar
)
}
artifacts {
runtimeArtifacts buildRegistrationTool
publish buildRegistrationTool
}
jar {
classifier "ignore"
}
publish {
name 'registration-tool'
disableDefaultJar = true
}
dependencies {
compile project(':node')
// TODO: remove this when ArgsParser is moved into corda.
compile project(':network-management')
testCompile 'junit:junit:4.12'
testCompile "org.assertj:assertj-core:${assertj_version}"
}

View File

@ -1,32 +0,0 @@
package com.r3.corda.networkmanage.registration
import com.r3.corda.networkmanage.registration.ToolOption.KeyCopierOption
import net.corda.nodeapi.internal.crypto.X509KeyStore
/**
* This utility will copy private key with [KeyCopierOption.sourceAlias] from provided source keystore and copy it to target
* keystore with the same alias, or [KeyCopierOption.destinationAlias] if provided.
*
* This tool uses Corda's security provider which support EdDSA keys.
*/
fun KeyCopierOption.copyKeystore() {
println("**************************************")
println("* *")
println("* Key copy tool *")
println("* *")
println("**************************************")
println()
println("This utility will copy private key with [srcalias] from provided source keystore and copy it to target keystore with the same alias, or [destalias] if provided.")
println()
// Read private key and certificates from notary identity keystore.
val srcKeystore = X509KeyStore.fromFile(sourceFile, sourcePassword ?: readPassword("Source key store password:"))
val srcPrivateKey = srcKeystore.getPrivateKey(sourceAlias)
val srcCertChain = srcKeystore.getCertificateChain(sourceAlias)
X509KeyStore.fromFile(destinationFile, destinationPassword ?: readPassword("Destination key store password:")).update {
val keyAlias = destinationAlias ?: sourceAlias
setPrivateKey(keyAlias, srcPrivateKey, srcCertChain)
println("Added '$keyAlias' to keystore : $destinationFile")
}
}

View File

@ -1,94 +0,0 @@
package com.r3.corda.networkmanage.registration
import com.r3.corda.networkmanage.common.utils.ArgsParser
import com.r3.corda.networkmanage.registration.ToolOption.KeyCopierOption
import com.r3.corda.networkmanage.registration.ToolOption.RegistrationOption
import joptsimple.OptionSet
import joptsimple.OptionSpecBuilder
import joptsimple.util.PathConverter
import joptsimple.util.PathProperties
import net.corda.core.crypto.Crypto
import java.nio.file.Path
fun main(args: Array<String>) {
Crypto.registerProviders() // Required to register Providers first thing on boot.
val options = ToolArgsParser().parseOrExit(*args, printHelpOn = System.out)
when (options) {
is RegistrationOption -> options.runRegistration()
is KeyCopierOption -> options.copyKeystore()
}
}
class ToolArgsParser : ArgsParser<ToolOption>() {
private val importKeyStoreArg = optionParser.accepts("importkeystore")
private val configFileArg = optionParser
.accepts("config-file", "Path to the registration config file")
.availableUnless(importKeyStoreArg)
.requiredUnless(importKeyStoreArg)
.withRequiredArg()
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
// key copy tool args
private val destKeystorePathArg = optionParser.accepts("destkeystore", "Path to the destination keystore which the private key should be copied to")
.requireOnlyIf(importKeyStoreArg)
.withRequiredArg()
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
private val srcKeystorePathArg = optionParser.accepts("srckeystore", "Path to the source keystore containing the private key")
.requireOnlyIf(importKeyStoreArg)
.withRequiredArg()
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
private val destPasswordArg = optionParser.accepts("deststorepass", "Source keystore password. Read in from the console if not specified.")
.availableIf(importKeyStoreArg)
.withRequiredArg()
private val srcPasswordArg = optionParser.accepts("srcstorepass", "Destination keystore password. Read in from the console if not specified.")
.availableIf(importKeyStoreArg)
.withRequiredArg()
private val destAliasArg = optionParser.accepts("destalias", "The alias under which the private key will be stored in the destination key store. If not provided then [srcalias] is used.")
.availableIf(importKeyStoreArg)
.withRequiredArg()
private val srcAliasArg = optionParser.accepts("srcalias", "The alias under which the private key resides in the source key store")
.requireOnlyIf(importKeyStoreArg)
.withRequiredArg()
override fun parse(optionSet: OptionSet): ToolOption {
val isCopyKey = optionSet.has(importKeyStoreArg)
return if (isCopyKey) {
val srcKeystorePath = optionSet.valueOf(srcKeystorePathArg)
val targetKeystorePath = optionSet.valueOf(destKeystorePathArg)
val srcPassword = optionSet.valueOf(srcPasswordArg)
val destPassword = optionSet.valueOf(destPasswordArg)
val srcAlias = optionSet.valueOf(srcAliasArg)
val destAlias = optionSet.valueOf(destAliasArg)
KeyCopierOption(srcKeystorePath, targetKeystorePath, srcPassword, destPassword, srcAlias, destAlias)
} else {
val configFile = optionSet.valueOf(configFileArg)
RegistrationOption(configFile)
}
}
}
private fun OptionSpecBuilder.requireOnlyIf(option: OptionSpecBuilder): OptionSpecBuilder = requiredIf(option).availableIf(option)
sealed class ToolOption {
data class RegistrationOption(val configFile: Path) : ToolOption()
data class KeyCopierOption(val sourceFile: Path,
val destinationFile: Path,
val sourcePassword: String?,
val destinationPassword: String?,
val sourceAlias: String,
val destinationAlias: String?) : ToolOption()
}
fun readPassword(fmt: String): String {
return if (System.console() != null) {
String(System.console().readPassword(fmt))
} else {
print(fmt)
readLine() ?: ""
}
}

View File

@ -1,72 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.registration
import com.r3.corda.networkmanage.registration.ToolOption.RegistrationOption
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole
import net.corda.core.internal.div
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.parseAs
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
const val NOTARY_PRIVATE_KEY_ALIAS = "${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key"
fun RegistrationOption.runRegistration() {
println("**********************************************************")
println("* *")
println("* Notary identity registration tool *")
println("* *")
println("**********************************************************")
println()
println("This tool will create a notary identity certificate signing request using information found in '$configFile'")
println()
val config = ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(false))
.resolve()
.parseAs<NotaryRegistrationConfig>()
val sslConfig = object : SSLConfiguration {
override val keyStorePassword: String by lazy { config.keyStorePassword ?: readPassword("Node Keystore password:") }
override val trustStorePassword: String by lazy { config.trustStorePassword ?: readPassword("Node TrustStore password:") }
val parent = configFile.parent
override val certificatesDirectory: Path = if (parent != null) parent / "certificates" else Paths.get("certificates")
override val nodeKeystore: Path get() = config.keystorePath ?: certificatesDirectory/"notaryidentitykeystore.jks"
override val crlCheckSoftFail: Boolean = config.crlCheckSoftFail
}
NetworkRegistrationHelper(sslConfig,
config.legalName,
config.email,
HTTPNetworkRegistrationService(config.compatibilityZoneURL),
config.networkRootTrustStorePath,
config.networkRootTrustStorePassword ?: readPassword("Network trust root password:"),
NOTARY_PRIVATE_KEY_ALIAS,
CertRole.SERVICE_IDENTITY).buildKeystore()
}
data class NotaryRegistrationConfig(val legalName: CordaX500Name,
val email: String,
val compatibilityZoneURL: URL,
val networkRootTrustStorePath: Path,
val keyStorePassword: String?,
val networkRootTrustStorePassword: String?,
val trustStorePassword: String?,
val keystorePath: Path?,
val crlCheckSoftFail: Boolean,
val crlDistributionPoint: URL? = null)

View File

@ -1,15 +0,0 @@
legalName {
organisationUnit = "R3 Corda"
organisation = "R3 LTD"
locality = "London"
country = "GB"
}
# legalName = "C=GB, L=London, O=R3 LTD, OU=R3 Corda"
email = "test@email.com"
compatibilityZoneURL = "http://doorman.url.com"
networkRootTrustStorePath = "networkRootTrustStore.jks"
certRole = "NODE_CA"
networkRootTrustStorePassword = "password"
keyStorePassword = "password"
trustStorePassword = "password"

View File

@ -1,47 +0,0 @@
package com.r3.corda.networkmanage.registration
import net.corda.core.crypto.Crypto
import net.corda.core.internal.div
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
import javax.security.auth.x500.X500Principal
class KeyCopyToolTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private val tempDir: Path by lazy { tempFolder.root.toPath() }
@Test
fun `key copy correctly`() {
val keyCopyOption = ToolOption.KeyCopierOption(
sourceFile = tempDir / "srcKeystore.jks",
destinationFile = tempDir / "destKeystore.jks",
sourcePassword = "srctestpass",
destinationPassword = "desttestpass",
sourceAlias = "TestKeyAlias",
destinationAlias = null)
// Prepare source and destination keystores
val keyPair = Crypto.generateKeyPair()
val cert = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Test"), keyPair)
X509KeyStore.fromFile(keyCopyOption.sourceFile, keyCopyOption.sourcePassword!!, createNew = true).update {
setPrivateKey(keyCopyOption.sourceAlias, keyPair.private, listOf(cert))
}
X509KeyStore.fromFile(keyCopyOption.destinationFile, keyCopyOption.destinationPassword!!, createNew = true)
// Copy private key from src keystore to dest keystore using the tool
keyCopyOption.copyKeystore()
// Verify key copied correctly
val destKeystore = X509KeyStore.fromFile(keyCopyOption.destinationFile, keyCopyOption.destinationPassword!!)
assertEquals(keyPair.private, destKeystore.getPrivateKey(keyCopyOption.sourceAlias, keyCopyOption.destinationPassword!!))
assertEquals(cert, destKeystore.getCertificate(keyCopyOption.sourceAlias))
}
}

View File

@ -1,92 +0,0 @@
package com.r3.corda.networkmanage.registration
import joptsimple.OptionException
import net.corda.core.internal.div
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.nio.file.Files
import java.nio.file.Path
class OptionParserTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
lateinit var tempDir: Path
@Before
fun setup() {
tempDir = tempFolder.root.toPath()
Files.createFile(tempDir / "test.file")
Files.createFile(tempDir / "source.jks")
Files.createFile(tempDir / "target.jks")
}
@Test
fun `parse registration args correctly`() {
val options = ToolArgsParser().parseOrExit("--config-file", "${tempDir / "test.file"}") as ToolOption.RegistrationOption
assertThat(options.configFile).isEqualTo(tempDir / "test.file")
}
@Test
fun `registration args should be unavailable in key copy mode`() {
val keyCopyArgs = arrayOf(
"--importkeystore",
"--srckeystore", "${tempDir / "source.jks"}",
"--srcstorepass", "password1",
"--destkeystore", "${tempDir / "target.jks"}",
"--deststorepass", "password2",
"--srcalias", "testalias")
assertThatThrownBy { ToolArgsParser().parseOrExit(*keyCopyArgs, "--config-file", "test.file", printHelpOn = null) }
.isInstanceOf(OptionException::class.java)
.hasMessageContaining("Option(s) [config-file] are unavailable given other options on the command line")
}
@Test
fun `key copy args should be unavailable in registration mode`() {
assertThatThrownBy {
ToolArgsParser().parseOrExit("--config-file", "${tempDir / "test.file"}", "--srckeystore", "${tempDir / "source.jks"}", printHelpOn = null)
}.isInstanceOf(OptionException::class.java)
.hasMessageContaining("Option(s) [srckeystore] are unavailable given other options on the command line")
}
@Test
fun `all import keystore options`() {
val keyCopyArgs = arrayOf(
"--importkeystore",
"--srckeystore", "${tempDir / "source.jks"}",
"--srcstorepass", "password1",
"--destkeystore", "${tempDir / "target.jks"}",
"--deststorepass", "password2",
"--srcalias", "testalias",
"--destalias", "testalias2")
assertThat(ToolArgsParser().parseOrExit(*keyCopyArgs)).isEqualTo(ToolOption.KeyCopierOption(
sourceFile = tempDir / "source.jks",
destinationFile = tempDir / "target.jks",
sourcePassword = "password1",
destinationPassword = "password2",
sourceAlias = "testalias",
destinationAlias = "testalias2"
))
}
@Test
fun `minimum import keystore options`() {
val keyCopyArgs = arrayOf(
"--importkeystore",
"--srckeystore", "${tempDir / "source.jks"}",
"--destkeystore", "${tempDir / "target.jks"}",
"--srcalias", "testalias")
assertThat(ToolArgsParser().parseOrExit(*keyCopyArgs)).isEqualTo(ToolOption.KeyCopierOption(
sourceFile = tempDir / "source.jks",
destinationFile = tempDir / "target.jks",
sourcePassword = null,
destinationPassword = null,
sourceAlias = "testalias",
destinationAlias = null
))
}
}

View File

@ -1,55 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.registration
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.config.parseAs
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import java.nio.file.Paths
class RegistrationConfigTest {
@Test
fun `parse config file correctly`() {
val testConfig = """
legalName {
organisationUnit = "R3 Corda"
organisation = "R3 LTD"
locality = "London"
country = "GB"
}
email = "test@email.com"
compatibilityZoneURL = "http://doorman.url.com"
networkRootTrustStorePath = "networkRootTrustStore.jks"
keystorePath = "notaryidentitykeystore.jks"
networkRootTrustStorePassword = "password"
keyStorePassword = "password"
trustStorePassword = "password"
crlCheckSoftFail = true
""".trimIndent()
val config = ConfigFactory.parseString(testConfig, ConfigParseOptions.defaults().setAllowMissing(false))
.resolve()
.parseAs<NotaryRegistrationConfig>()
assertEquals(CordaX500Name.parse("OU=R3 Corda, O=R3 LTD, L=London, C=GB"), config.legalName)
assertEquals("http://doorman.url.com", config.compatibilityZoneURL.toString())
assertEquals("test@email.com", config.email)
assertEquals(Paths.get("networkRootTrustStore.jks"), config.networkRootTrustStorePath)
assertEquals("password", config.networkRootTrustStorePassword)
assertEquals(Paths.get("notaryidentitykeystore.jks"), config.keystorePath)
assertTrue(config.crlCheckSoftFail)
}
}

View File

@ -1,190 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage
import CryptoServerAPI.CryptoServerException
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.spotify.docker.client.DefaultDockerClient
import com.spotify.docker.client.DockerClient
import com.spotify.docker.client.messages.ContainerConfig
import com.spotify.docker.client.messages.HostConfig
import com.spotify.docker.client.messages.PortBinding
import com.spotify.docker.client.messages.RegistryAuth
import net.corda.core.utilities.loggerFor
import net.corda.testing.core.freeLocalHostAndPort
import org.junit.Assume.assumeFalse
import org.junit.rules.ExternalResource
data class CryptoUserCredentials(val username: String, val password: String)
/**
* HSM Simulator rule allowing to use the HSM Simulator within the integration tests. It is designed to be used mainly
* on the TeamCity, but if the required setup is available it can be executed locally as well.
* It will bind to the simulator to the local port
* To use it locally, the following pre-requisites need to be met:
* 1) Docker engine needs to be installed on the machine
* 2) Environment variables (AZURE_CR_USER and AZURE_CR_PASS) are available and hold valid credentials to the corda.azurecr.io
* repository
* 3) HSM requires Unlimited Strength Jurisdiction extension to be installed on the machine connecting with the HSM box.
* See http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
*
* Since the above setup is not a strong requirement for the integration tests to be executed it is intended that this
* rule is used together with the assumption mechanism in tests.
*/
class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS,
private val imageRepoTag: String = DEFAULT_IMAGE_REPO_TAG,
private val imageVersion: String = DEFAULT_IMAGE_VERSION,
private val pullImage: Boolean = DEFAULT_PULL_IMAGE,
private val registryUser: String? = REGISTRY_USERNAME,
private val registryPass: String? = REGISTRY_PASSWORD) : ExternalResource() {
private companion object {
val DEFAULT_SERVER_ADDRESS = "corda.azurecr.io"
/*
* Currently we have following images:
* 1) corda.azurecr.io/network-management/hsm-simulator - having only one user configured:
* - INTEGRATION_TEST (password: INTEGRATION_TEST) with the CXI_GROUP="*"
* 2)corda.azurecr.io/network-management/hsm-simulator-with-groups - having following users configured:
* - INTEGRATION_TEST (password: INTEGRATION_TEST) with the CXI_GROUP=*
* - INTEGRATION_TEST_SUPER (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT
* - INTEGRATION_TEST_ROOT (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.ROOT
* - INTEGRATION_TEST_OPS (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.OPS
* - INTEGRATION_TEST_SUPER_ (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.*
* - INTEGRATION_TEST_ROOT_ (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.ROOT.*
* - INTEGRATION_TEST_OPS_ (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.OPS.*
* - INTEGRATION_TEST_OPS_CERT (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.OPS.CERT
* - INTEGRATION_TEST_OPS_NETMAP (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.OPS.NETMAP
* - INTEGRATION_TEST_OPS_CERT (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.OPS.CERT.*
* - INTEGRATION_TEST_OPS_NETMAP (password: INTEGRATION_TEST) with the CXI_GROUP=TEST.CORDACONNECT.OPS.NETMAP.*
*/
val DEFAULT_IMAGE_REPO_TAG = "corda.azurecr.io/network-management/hsm-simulator-with-groups"
val DEFAULT_IMAGE_VERSION = "latest"
val DEFAULT_PULL_IMAGE = true
val HSM_SIMULATOR_PORT = "3001/tcp"
val CONTAINER_KILL_TIMEOUT_SECONDS = 10
val CRYPTO_USER = System.getProperty("CRYPTO_USER", "INTEGRATION_TEST")
val CRYPTO_PASSWORD = System.getProperty("CRYPTO_PASSWORD", "INTEGRATION_TEST")
val REGISTRY_USERNAME = System.getenv("AZURE_CR_USER")
val REGISTRY_PASSWORD = System.getenv("AZURE_CR_PASS")
val log = loggerFor<HsmSimulator>()
private val HSM_STARTUP_SLEEP_INTERVAL_MS = 500L
private val HSM_STARTUP_POLL_MAX_COUNT = 10;
}
private val localHostAndPortBinding = freeLocalHostAndPort()
private lateinit var docker: DockerClient
private var containerId: String? = null
override fun before() {
assumeFalse("Docker registry username is not set!. Skipping the test.", registryUser.isNullOrBlank())
assumeFalse("Docker registry password is not set!. Skipping the test.", registryPass.isNullOrBlank())
docker = DefaultDockerClient.fromEnv().build()
if (pullImage) {
docker.pullHsmSimulatorImageFromRepository()
}
containerId = docker.createContainer()
docker.startHsmSimulatorContainer()
}
override fun after() {
docker.stopAndRemoveHsmSimulatorContainer()
}
/**
* Retrieves the port at which simulator is listening at.
*/
val port get(): Int = localHostAndPortBinding.port
/**
* Retrieves the host IP address, which the simulator is listening at.
*/
val host get(): String = localHostAndPortBinding.host
/**
* Retrieves the HSM user credentials. Those are supposed to be preconfigured on the HSM itself. Thus, when
* tests are executed these credentials can be used to access HSM crypto user's functionality.
* It is assumed that the docker image state has those configured already and they should match the ones returned.
*/
fun cryptoUserCredentials(): CryptoUserCredentials {
return CryptoUserCredentials(CRYPTO_USER, CRYPTO_PASSWORD)
}
private fun DockerClient.stopAndRemoveHsmSimulatorContainer() {
if (containerId != null) {
log.debug("Stopping container $containerId...")
this.stopContainer(containerId, CONTAINER_KILL_TIMEOUT_SECONDS)
log.debug("Removing container $containerId...")
this.removeContainer(containerId)
}
}
private fun DockerClient.startHsmSimulatorContainer() {
if (containerId != null) {
log.debug("Starting container $containerId...")
this.startContainer(containerId)
pollAndWaitForHsmSimulator()
}
}
private fun pollAndWaitForHsmSimulator() {
val config = CryptoServerProviderConfig(
Device = "${localHostAndPortBinding.port}@${localHostAndPortBinding.host}",
KeyGroup = "*",
KeySpecifier = -1
)
var pollCount = HSM_STARTUP_POLL_MAX_COUNT
while (pollCount > 0) {
val provider = createProvider(config)
try {
provider.loginPassword(CRYPTO_USER, CRYPTO_PASSWORD)
provider.cryptoServer.authState
return
} catch (e: CryptoServerException) {
pollCount--
Thread.sleep(HSM_STARTUP_SLEEP_INTERVAL_MS)
} finally {
provider.logoff()
}
}
throw IllegalStateException("Unable to obtain connection to initialised HSM Simulator")
}
private fun getImageFullName() = "$imageRepoTag:$imageVersion"
private fun DockerClient.pullHsmSimulatorImageFromRepository(): DockerClient {
this.pull(imageRepoTag,
RegistryAuth.builder()
.serverAddress(serverAddress)
.username(registryUser)
.password(registryPass)
.build())
return this
}
private fun DockerClient.createContainer(): String? {
val portBindings = mapOf(HSM_SIMULATOR_PORT to listOf(PortBinding.create(localHostAndPortBinding.host, localHostAndPortBinding.port.toString())))
val hostConfig = HostConfig.builder().portBindings(portBindings).build()
val containerConfig = ContainerConfig.builder()
.hostConfig(hostConfig)
.portSpecs()
.image(getImageFullName())
.exposedPorts(HSM_SIMULATOR_PORT)
.build()
val containerCreation = this.createContainer(containerConfig)
return containerCreation.id()
}
}

View File

@ -1,203 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.HsmSimulator
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.authentication.InputReader
import com.r3.corda.networkmanage.hsm.configuration.*
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import com.r3.corda.networkmanage.hsm.generator.certificate.CertificateConfiguration
import com.r3.corda.networkmanage.hsm.generator.certificate.GeneratorParameters
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.node.internal.makeTestDataSourceProperties
import net.corda.testing.node.internal.makeTestDatabaseProperties
import org.junit.Before
import org.junit.ClassRule
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.net.URL
import java.nio.file.Path
import java.util.*
import com.r3.corda.networkmanage.hsm.authentication.AuthMode as SigningServiceAuthMode
import com.r3.corda.networkmanage.hsm.generator.AuthMode as GeneratorAuthMode
abstract class HsmBaseTest : IntegrationTest() {
companion object {
const val ROOT_CERT_KEY_GROUP = "TEST.CORDACONNECT.ROOT"
const val NETWORK_MAP_CERT_KEY_GROUP = "TEST.CORDACONNECT.OPS.NETMAP"
const val DOORMAN_CERT_KEY_GROUP = "TEST.CORDACONNECT.OPS.CERT"
const val ROOT_CERT_SUBJECT = "CN=Corda Root CA, OU=Corda, O=R3 Ltd, L=London, C=GB"
const val NETWORK_MAP_CERT_SUBJECT = "CN=Corda Network Map, OU=Corda, O=R3 Ltd, L=London, C=GB"
const val DOORMAN_CERT_SUBJECT = "CN=Corda Doorman CA, OU=Corda, O=R3 Ltd, L=London, C=GB"
const val TRUSTSTORE_PASSWORD: String = "trustpass"
const val HSM_USERNAME = "INTEGRATION_TEST"
const val HSM_PASSWORD = "INTEGRATION_TEST"
const val HSM_USERNAME_SUPER = "INTEGRATION_TEST_SUPER"
const val HSM_USERNAME_OPS = "INTEGRATION_TEST_OPS"
const val HSM_USERNAME_ROOT = "INTEGRATION_TEST_ROOT"
const val HSM_USERNAME_SUPER_ = "INTEGRATION_TEST_SUPER_"
const val HSM_USERNAME_OPS_ = "INTEGRATION_TEST_OPS_"
const val HSM_USERNAME_ROOT_ = "INTEGRATION_TEST_ROOT_"
const val HSM_USERNAME_OPS_CERT = "INTEGRATION_TEST_OPS_CERT"
const val HSM_USERNAME_OPS_NETMAP = "INTEGRATION_TEST_OPS_NETMAP"
const val HSM_USERNAME_OPS_CERT_ = "INTEGRATION_TEST_OPS_CERT_"
const val HSM_USERNAME_OPS_NETMAP_ = "INTEGRATION_TEST_OPS_NETMAP_"
val HSM_USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME)
val HSM_SUPER_USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_SUPER)
val HSM_ROOT_USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_ROOT)
val HSM_OPS_USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_OPS)
val HSM_SUPER__USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_SUPER_)
val HSM_ROOT__USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_ROOT_)
val HSM_OPS__USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_OPS_)
val HSM_OPS_CERT_USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_OPS_CERT)
val HSM_OPS_NETMAP_USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_OPS_NETMAP)
val HSM_OPS_CERT__USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_OPS_CERT_)
val HSM_OPS_NETMAP__USER_CONFIGS = createHsmUserConfigs(HSM_USERNAME_OPS_NETMAP_)
private fun createHsmUserConfigs(username: String): List<UserAuthenticationParameters> {
return listOf(UserAuthenticationParameters(
username = username,
authMode = GeneratorAuthMode.PASSWORD,
authToken = "INTEGRATION_TEST",
keyFilePassword = null))
}
@ClassRule
@JvmField
val databaseSchemas = IntegrationTestSchemas(DOORMAN_DB_NAME)
}
protected lateinit var rootKeyStoreFile: Path
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Rule
@JvmField
val hsmSimulator: HsmSimulator = HsmSimulator()
private lateinit var dbName: String
@Before
fun generateDbName() {
rootKeyStoreFile = tempFolder.root.toPath() / "truststore.jks"
dbName = random63BitValue().toString()
}
private fun createGeneratorParameters(certConfig: CertificateConfiguration,
userConfigs: List<UserAuthenticationParameters>): GeneratorParameters {
return GeneratorParameters(
hsmHost = hsmSimulator.host,
hsmPort = hsmSimulator.port,
trustStoreDirectory = rootKeyStoreFile.parent,
trustStorePassword = TRUSTSTORE_PASSWORD,
userConfigs = userConfigs,
certConfig = certConfig
)
}
protected fun createGeneratorParameters(keyGroup: String,
rootKeyGroup: String?,
certificateType: CertificateType,
subject: String,
hsmUserConfigs: List<UserAuthenticationParameters> = HSM_USER_CONFIGS): GeneratorParameters {
return createGeneratorParameters(CertificateConfiguration(
keySpecifier = 1,
keyGroup = keyGroup,
storeKeysExternal = false,
rootKeyGroup = rootKeyGroup,
subject = subject,
validDays = 3650,
keyCurve = "NIST-P256",
certificateType = certificateType,
keyExport = 0,
keyGenMechanism = 4,
keyOverride = 0,
crlIssuer = null,
crlDistributionUrl = null
), hsmUserConfigs)
}
protected fun createHsmSigningServiceConfig(doormanCertConfig: DoormanCertificateConfig?,
networkMapCertificateConfig: NetworkMapCertificateConfig?): SigningServiceConfig {
return SigningServiceConfig(
dataSourceProperties = mock(),
device = "${hsmSimulator.port}@${hsmSimulator.host}",
keySpecifier = 1,
doorman = doormanCertConfig,
networkMap = networkMapCertificateConfig
)
}
protected fun createDoormanCertificateConfig(): DoormanCertificateConfig {
return DoormanCertificateConfig(
rootKeyStoreFile = rootKeyStoreFile,
keyGroup = DOORMAN_CERT_KEY_GROUP,
validDays = 3650,
rootKeyStorePassword = TRUSTSTORE_PASSWORD,
crlDistributionPoint = URL("http://test.com/revoked.crl"),
crlServerSocketAddress = NetworkHostAndPort("test.com", 4555),
crlUpdatePeriod = 1000,
mode = ManualMode.CSR,
authParameters = AuthParametersConfig(
mode = SigningServiceAuthMode.PASSWORD,
threshold = 2
)
)
}
protected fun createNetworkMapCertificateConfig(): NetworkMapCertificateConfig {
return NetworkMapCertificateConfig(
username = "INTEGRATION_TEST",
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
authParameters = AuthParametersConfig(
mode = SigningServiceAuthMode.PASSWORD,
password = "INTEGRATION_TEST",
threshold = 2
)
)
}
protected fun givenHsmUserAuthenticationInput(username: String = HSM_USERNAME,
password: String = HSM_PASSWORD): InputReader {
val inputReader = mock<InputReader>()
whenever(inputReader.readLine()).thenReturn(username)
whenever(inputReader.readPassword(any())).thenReturn(password)
return inputReader
}
fun makeTestDataSourceProperties(): Properties {
return makeTestDataSourceProperties(DOORMAN_DB_NAME, configSupplier = configSupplierForSupportedDatabases())
}
fun makeTestDatabaseProperties(): DatabaseConfig {
return makeTestDatabaseProperties(DOORMAN_DB_NAME, configSupplier = configSupplierForSupportedDatabases())
}
protected fun createProviderConfig(keyGroup: String): CryptoServerProviderConfig {
return CryptoServerProviderConfig(
Device = "${hsmSimulator.port}@${hsmSimulator.host}",
KeySpecifier = 1,
KeyGroup = keyGroup,
StoreKeysExternal = false)
}
}

View File

@ -1,63 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common
import com.r3.corda.networkmanage.common.utils.createSignedCrl
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import net.corda.core.crypto.SecureHash
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.testing.database.DatabaseConstants
import net.corda.testing.node.internal.databaseProviderDataSourceConfig
import org.apache.commons.io.FileUtils
import org.junit.rules.TemporaryFolder
import java.net.URL
import java.nio.file.Path
const val HOST = "localhost"
const val DOORMAN_DB_NAME = "doorman"
fun networkMapInMemoryH2DataSourceConfig(nodeName: String? = null, postfix: String? = null): Config {
val nodeName = nodeName ?: SecureHash.randomSHA256().toString()
val h2InstanceName = if (postfix != null) nodeName + "_" + postfix else nodeName
return ConfigFactory.parseMap(mapOf(
DatabaseConstants.DATA_SOURCE_CLASSNAME to "org.h2.jdbcx.JdbcDataSource",
DatabaseConstants.DATA_SOURCE_URL to "jdbc:h2:mem:${h2InstanceName};DB_CLOSE_DELAY=-1",
DatabaseConstants.DATA_SOURCE_USER to "sa",
DatabaseConstants.DATA_SOURCE_PASSWORD to ""))
}
fun generateEmptyCrls(tempFolder: TemporaryFolder, rootCertAndKeyPair: CertificateAndKeyPair, directEndpoint: URL, indirectEndpoint: URL): Pair<Path, Path> {
val localSigner = LocalSigner(rootCertAndKeyPair)
val directCrl = createSignedCrl(rootCertAndKeyPair.certificate, directEndpoint, 10.days, localSigner, emptyList(), false)
val indirectCrl = createSignedCrl(rootCertAndKeyPair.certificate, indirectEndpoint, 10.days, localSigner, emptyList(), true)
val directCrlFile = tempFolder.newFile()
FileUtils.writeByteArrayToFile(directCrlFile, directCrl.encoded)
val indirectCrlFile = tempFolder.newFile()
FileUtils.writeByteArrayToFile(indirectCrlFile, indirectCrl.encoded)
return Pair(directCrlFile.toPath(), indirectCrlFile.toPath())
}
fun getCaCrlEndpoint(serverAddress: NetworkHostAndPort) = URL("http://$serverAddress/certificate-revocation-list/root")
fun getEmptyCrlEndpoint(serverAddress: NetworkHostAndPort) = URL("http://$serverAddress/certificate-revocation-list/empty")
fun getNodeCrlEndpoint(serverAddress: NetworkHostAndPort) = URL("http://$serverAddress/certificate-revocation-list/doorman")
//TODO add more dbs to test once doorman supports them
fun configSupplierForSupportedDatabases(): (String?, String?) -> Config =
when (System.getProperty("custom.databaseProvider", "")) {
"integration-sql-server", "integration-azure-sql" -> ::databaseProviderDataSourceConfig
else -> { _, _ -> ConfigFactory.empty() }
}

View File

@ -1,188 +0,0 @@
package com.r3.corda.networkmanage.doorman
import com.r3.corda.networkmanage.common.DOORMAN_DB_NAME
import com.r3.corda.networkmanage.common.configSupplierForSupportedDatabases
import com.r3.corda.networkmanage.common.networkMapInMemoryH2DataSourceConfig
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.internal.div
import net.corda.core.internal.readObject
import net.corda.core.messaging.ParametersUpdateInfo
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.*
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.internal.NodeHandleInternal
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.internal.CompatibilityZoneParams
import net.corda.testing.node.internal.internalDriver
import net.corda.testing.node.internal.makeTestDataSourceProperties
import org.assertj.core.api.Assertions.assertThat
import org.junit.*
import org.junit.Assert.assertEquals
import java.net.URL
import java.security.cert.X509Certificate
import java.time.Instant
class NetworkParametersUpdateTest : IntegrationTest() {
companion object {
private val timeoutMillis = 5.seconds.toMillis()
@ClassRule
@JvmField
val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName(),
DUMMY_NOTARY_NAME.toDatabaseSchemaName(), DOORMAN_DB_NAME)
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val portAllocation = PortAllocation.Incremental(10000)
private val serverAddress = portAllocation.nextHostAndPort()
private lateinit var rootCaCert: X509Certificate
private lateinit var doormanCa: CertificateAndKeyPair
private lateinit var networkMapCa: CertificateAndKeyPair
//for normal integration tests (not against standalone db) to create a unique db name for each tests against H2
private lateinit var dbNamePostfix: String
private var server: NetworkManagementServer? = null
@Before
fun init() {
dbNamePostfix = random63BitValue().toString()
val (rootCa, doormanCa) = createDevIntermediateCaCertPath()
rootCaCert = rootCa.certificate
this.doormanCa = doormanCa
networkMapCa = createDevNetworkMapCa(rootCa)
}
@After
fun cleanUp() {
server?.close()
}
@Test
fun `network parameters update commnunicated to node`() {
// Initialise the server with some network parameters
val initialNetParams = NetworkParametersCmd.Set(
notaries = emptyList(),
minimumPlatformVersion = 1,
maxMessageSize = 1_000_000,
maxTransactionSize = 1_000_000,
parametersUpdate = null,
eventHorizonDays = 30
)
applyNetworkParametersAndStart(initialNetParams)
val compatibilityZone = CompatibilityZoneParams(
URL("http://$serverAddress"),
publishNotaries = {},
rootCert = rootCaCert
)
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
notarySpecs = emptyList(),
initialiseSerialization = false,
extraCordappPackagesToScan = listOf("net.corda.finance"),
notaryCustomOverrides = mapOf("devMode" to false)
) {
var (alice, bob) = listOf(
startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)),
startNode(providedName = BOB_NAME, customOverrides = mapOf("devMode" to false))
).map { it.getOrThrow() as NodeHandleInternal }
// Make sure that stopping Bob doesn't remove him from the network map
bob.stop()
Thread.sleep(timeoutMillis * 2)
assertThat(alice.rpc.networkMapSnapshot().map { it.legalIdentities[0].name }).contains(BOB_NAME)
val snapshot = alice.rpc.networkParametersFeed().snapshot
val updates = alice.rpc.networkParametersFeed().updates.bufferUntilSubscribed()
assertThat(snapshot).isNull()
val updateDeadline = Instant.now() + 10.seconds
applyNetworkParametersAndStart(initialNetParams.copy(
maxTransactionSize = 1_000_001,
parametersUpdate = ParametersUpdateConfig(
description = "Very Important Update",
updateDeadline = updateDeadline
)
))
updates.expectEvents(isStrict = true) {
sequence(
expect { update: ParametersUpdateInfo ->
assertEquals(update.description, "Very Important Update")
assertEquals(update.parameters.maxTransactionSize, 1_000_001)
assertEquals(update.parameters.epoch, 2) // The epoch must increment automatically.
}
)
}
val paramUpdateInfo = alice.rpc.networkParametersFeed().snapshot!!
alice.rpc.acceptNewNetworkParameters(paramUpdateInfo.hash)
// Make sure we've passed the deadline
Thread.sleep(Math.max(updateDeadline.toEpochMilli() - System.currentTimeMillis(), 0))
applyNetworkParametersAndStart(NetworkParametersCmd.FlagDay)
alice.stop()
alice = startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() as NodeHandleInternal
// TODO It is also possible to check what version of parameters node runs by writing flow that reads that value from ServiceHub
val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
.readObject<SignedNetworkParameters>().verified()
assertEquals(networkParameters, paramUpdateInfo.parameters)
assertThat(alice.rpc.networkParametersFeed().snapshot).isNull() // Check that NMS doesn't advertise updates anymore.
// Check that Bob is no longer on the network as it didn't accept the new parameteres.
assertThat(alice.rpc.networkMapSnapshot().map { it.legalIdentities[0].name }).doesNotContain(BOB_NAME)
}
}
private fun startServer(startNetworkMap: Boolean = true): NetworkManagementServer {
val doormanConfig = DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis)
val server = NetworkManagementServer(makeTestDataSourceProperties(DOORMAN_DB_NAME, dbNamePostfix, configSupplier = configSupplierForSupportedDatabases(), fallBackConfigSupplier = ::networkMapInMemoryH2DataSourceConfig),
DatabaseConfig(runMigration = true), doormanConfig, null)
server.start(
serverAddress,
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
if (startNetworkMap) {
NetworkMapStartParams(
LocalSigner(networkMapCa),
NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis)
)
} else {
null
}
)
return server
}
private fun applyNetworkParametersAndStart(networkParametersCmd: NetworkParametersCmd) {
server?.close()
NetworkManagementServer(
makeTestDataSourceProperties(DOORMAN_DB_NAME, dbNamePostfix, configSupplier = configSupplierForSupportedDatabases(), fallBackConfigSupplier = ::networkMapInMemoryH2DataSourceConfig),
DatabaseConfig(runMigration = true),
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
null).use {
it.netParamsUpdateHandler.processNetworkParameters(networkParametersCmd)
}
server = startServer(startNetworkMap = true)
// Wait for server to process the parameters update and for the nodes to poll again
Thread.sleep(timeoutMillis * 2)
}
}

View File

@ -1,215 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.r3.corda.networkmanage.common.*
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.cordform.CordformNode
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.internal.list
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.internal.NodeHandleInternal
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.CompatibilityZoneParams
import net.corda.testing.node.internal.internalDriver
import net.corda.testing.node.internal.makeTestDataSourceProperties
import net.corda.testing.node.internal.makeTestDatabaseProperties
import org.assertj.core.api.Assertions.assertThat
import org.junit.*
import org.junit.rules.TemporaryFolder
import java.net.URL
import java.nio.file.Path
import java.security.cert.X509Certificate
import kotlin.streams.toList
// This is the same test as the one in net.corda.node.utilities.registration but using the real doorman and with some
// extra checks on the network map.
class NodeRegistrationTest : IntegrationTest() {
companion object {
private val notaryName = CordaX500Name("NotaryService", "Zurich", "CH")
private val aliceName = CordaX500Name("Alice", "London", "GB")
private val genevieveName = CordaX500Name("Genevieve", "London", "GB")
private val timeoutMillis = 5.seconds.toMillis()
@ClassRule
@JvmField
val databaseSchemas = IntegrationTestSchemas(notaryName.organisation, aliceName.organisation, genevieveName.organisation,
DOORMAN_DB_NAME)
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val portAllocation = PortAllocation.Incremental(10000)
private val serverAddress = portAllocation.nextHostAndPort()
private lateinit var rootCaCert: X509Certificate
private lateinit var doormanCa: CertificateAndKeyPair
private lateinit var networkMapCa: CertificateAndKeyPair
//for normal integration tests (not against standalone db) to create a unique db name for each tests against H2
private lateinit var dbNamePostfix: String
private var server: NetworkManagementServer? = null
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private val doormanConfig: DoormanConfig get() = DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis)
private lateinit var revocationConfig: CertificateRevocationConfig
private fun createCertificateRevocationConfig(emptyCrlPath: Path, caCrlPath: Path): CertificateRevocationConfig {
return CertificateRevocationConfig(
approveAll = true,
jira = null,
approveInterval = timeoutMillis,
crlCacheTimeout = timeoutMillis,
localSigning = CertificateRevocationConfig.LocalSigning(
crlEndpoint = getNodeCrlEndpoint(serverAddress),
crlUpdateInterval = timeoutMillis),
emptyCrlPath = emptyCrlPath,
caCrlPath = caCrlPath)
}
@Before
fun init() {
dbNamePostfix = random63BitValue().toString()
val (rootCa, doormanCa) = createDevIntermediateCaCertPath()
rootCaCert = rootCa.certificate
this.doormanCa = doormanCa
networkMapCa = createDevNetworkMapCa(rootCa)
val (caCrlPath, emptyCrlPath) = generateEmptyCrls(tempFolder, rootCa, getCaCrlEndpoint(serverAddress), getEmptyCrlEndpoint(serverAddress))
revocationConfig = createCertificateRevocationConfig(emptyCrlPath, caCrlPath)
}
@After
fun cleanUp() {
server?.close()
}
@Test
fun `register nodes with doorman and then they transact with each other`() {
// Start the server without the network parameters config which won't start the network map. Just the doorman
// registration process will start up, allowing us to register the notaries which will then be used in the network
// parameters.
server = startServer(startNetworkMap = false)
val compatibilityZone = CompatibilityZoneParams(
URL("http://$serverAddress"),
publishNotaries = { notaryInfos ->
val setNetParams = NetworkParametersCmd.Set(
notaries = notaryInfos,
minimumPlatformVersion = 1,
maxMessageSize = 10485760,
maxTransactionSize = 10485760,
parametersUpdate = null,
eventHorizonDays = 30
)
// Restart the server once we're able to generate the network parameters
applyNetworkParametersAndStart(setNetParams)
},
rootCert = rootCaCert
)
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
notarySpecs = listOf(NotarySpec(notaryName)),
extraCordappPackagesToScan = listOf("net.corda.finance"),
notaryCustomOverrides = mapOf("devMode" to false)
) {
val (alice, notary) = listOf(
startNode(providedName = aliceName, customOverrides = mapOf("devMode" to false)),
defaultNotaryNode
).map { it.getOrThrow() as NodeHandleInternal }
alice.onlySeesFromNetworkMap(alice, notary)
notary.onlySeesFromNetworkMap(alice, notary)
val genevieve = startNode(providedName = genevieveName, customOverrides = mapOf("devMode" to false)).getOrThrow() as NodeHandleInternal
// Wait for the nodes to poll again
Thread.sleep(timeoutMillis * 2)
// Make sure the new node is visible to everyone
alice.onlySeesFromNetworkMap(alice, genevieve, notary)
notary.onlySeesFromNetworkMap(alice, genevieve, notary)
genevieve.onlySeesFromNetworkMap(alice, genevieve, notary)
// Check the nodes can communicate among themselves (and the notary).
val anonymous = false
genevieve.rpc.startFlow(
::CashIssueAndPaymentFlow,
1000.DOLLARS,
OpaqueBytes.of(12),
alice.nodeInfo.singleIdentity(),
anonymous,
defaultNotaryIdentity
).returnValue.getOrThrow()
}
}
private fun NodeHandleInternal.onlySeesFromNetworkMap(vararg nodes: NodeHandle) {
// Make sure the nodes aren't getting the node infos from their additional directories
val nodeInfosDir = configuration.baseDirectory / CordformNode.NODE_INFO_DIRECTORY
if (nodeInfosDir.exists()) {
assertThat(nodeInfosDir.list { it.toList() }).isEmpty()
}
assertThat(rpc.networkMapSnapshot()).containsOnlyElementsOf(nodes.map { it.nodeInfo })
}
private fun startServer(startNetworkMap: Boolean = true): NetworkManagementServer {
val server = NetworkManagementServer(makeTestDataSourceProperties(DOORMAN_DB_NAME, dbNamePostfix, configSupplier = configSupplierForSupportedDatabases(), fallBackConfigSupplier = ::networkMapInMemoryH2DataSourceConfig),
makeTestDatabaseProperties(DOORMAN_DB_NAME, configSupplier = configSupplierForSupportedDatabases()), doormanConfig, revocationConfig)
server.start(
serverAddress,
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
if (startNetworkMap) {
NetworkMapStartParams(
LocalSigner(networkMapCa),
NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis)
)
} else {
null
}
)
return server
}
private fun applyNetworkParametersAndStart(networkParametersCmd: NetworkParametersCmd) {
server?.close()
NetworkManagementServer(makeTestDataSourceProperties(DOORMAN_DB_NAME, dbNamePostfix, configSupplier = configSupplierForSupportedDatabases(), fallBackConfigSupplier = ::networkMapInMemoryH2DataSourceConfig),
makeTestDatabaseProperties(DOORMAN_DB_NAME, configSupplier = configSupplierForSupportedDatabases()), doormanConfig, revocationConfig).use {
it.netParamsUpdateHandler.processNetworkParameters(networkParametersCmd)
}
server = startServer(startNetworkMap = true)
// Wait for server to process the parameters update and for the nodes to poll again
Thread.sleep(timeoutMillis * 2)
}
}

View File

@ -1,40 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm
import com.r3.corda.networkmanage.common.HsmBaseTest
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import org.junit.Test
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertTrue
class HsmAuthenticatorTest : HsmBaseTest() {
@Test
fun `Authenticator executes the block once user is successfully authenticated`() {
// given
val userInput = givenHsmUserAuthenticationInput()
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val doormanCertificateConfig = hsmSigningServiceConfig.doorman!!
val authenticator = Authenticator(provider = createProvider(
doormanCertificateConfig.keyGroup,
hsmSigningServiceConfig.keySpecifier,
hsmSigningServiceConfig.device), inputReader = userInput)
val executed = AtomicBoolean(false)
// when
authenticator.connectAndAuthenticate { _, _ -> executed.set(true) }
// then
assertTrue(executed.get())
}
}

View File

@ -1,137 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.common.HsmBaseTest
import com.r3.corda.networkmanage.hsm.authentication.InputReader
import com.r3.corda.networkmanage.hsm.generator.AutoAuthenticator
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import com.r3.corda.networkmanage.hsm.generator.crl.CrlConfig
import com.r3.corda.networkmanage.hsm.generator.crl.GeneratorConfig
import com.r3.corda.networkmanage.hsm.generator.crl.RevocationConfig
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import org.apache.commons.io.FileUtils
import org.junit.Before
import org.junit.Test
import java.net.URL
import java.security.cert.CertificateFactory
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import com.r3.corda.networkmanage.hsm.generator.certificate.run as runCertificateGeneration
import com.r3.corda.networkmanage.hsm.generator.crl.run as runCrlGeneration
class HsmEmptyCrlGenerationTest : HsmBaseTest() {
private lateinit var inputReader: InputReader
@Before
override fun setUp() {
super.setUp()
inputReader = mock()
whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username)
whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password)
}
@Test
fun `An empty CRL is generated`() {
// when root cert is created
runCertificateGeneration(createGeneratorParameters(
keyGroup = ROOT_CERT_KEY_GROUP,
rootKeyGroup = null,
certificateType = CertificateType.ROOT_CA,
subject = ROOT_CERT_SUBJECT
))
// then root cert is persisted in the HSM
AutoAuthenticator(createProviderConfig(ROOT_CERT_KEY_GROUP), HSM_USER_CONFIGS).connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCert.issuerX500Principal, rootCert.subjectX500Principal)
}
val generatedFile = tempFolder.newFile()
runCrlGeneration(createCrlGeneratorParameters(CrlConfig(
crlEndpoint = URL("http://test.com/crl"),
filePath = generatedFile.toPath(),
keyGroup = ROOT_CERT_KEY_GROUP,
keySpecifier = 1,
validDays = 1000,
indirectIssuer = true,
revocations = emptyList()), HSM_ROOT_USER_CONFIGS))
val crl = CertificateFactory.getInstance("X.509")
.generateCRL(FileUtils.readFileToByteArray(generatedFile).inputStream()) as X509CRL
assertNotNull(crl)
assertEquals(ROOT_CERT_SUBJECT, crl.issuerDN.name)
assertTrue { crl.revokedCertificates.isEmpty() }
}
@Test
fun `A non-empty CRL is generated`() {
// when root cert is created
runCertificateGeneration(createGeneratorParameters(
keyGroup = ROOT_CERT_KEY_GROUP,
rootKeyGroup = null,
certificateType = CertificateType.ROOT_CA,
subject = ROOT_CERT_SUBJECT
))
// then root cert is persisted in the HSM
AutoAuthenticator(createProviderConfig(ROOT_CERT_KEY_GROUP), HSM_USER_CONFIGS).connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCert.issuerX500Principal, rootCert.subjectX500Principal)
}
val generatedFile = tempFolder.newFile()
val revokedSerialNumber = "1234567890"
runCrlGeneration(createCrlGeneratorParameters(CrlConfig(
crlEndpoint = URL("http://test.com/crl"),
filePath = generatedFile.toPath(),
keyGroup = ROOT_CERT_KEY_GROUP,
keySpecifier = 1,
validDays = 1000,
indirectIssuer = false,
revocations = listOf(
RevocationConfig(
certificateSerialNumber = "1234567890",
dateInMillis = 0,
reason = "KEY_COMPROMISE"
)
)), HSM_ROOT_USER_CONFIGS))
val crl = CertificateFactory.getInstance("X.509")
.generateCRL(FileUtils.readFileToByteArray(generatedFile).inputStream()) as X509CRL
assertNotNull(crl)
assertEquals(ROOT_CERT_SUBJECT, crl.issuerDN.name)
assertEquals(1, crl.revokedCertificates.size)
val revoked = crl.revokedCertificates.first()
assertEquals(revoked.serialNumber.toString(), revokedSerialNumber)
}
private fun createCrlGeneratorParameters(crlConfg: CrlConfig,
userConfigs: List<UserAuthenticationParameters>): GeneratorConfig {
return GeneratorConfig(
hsmHost = hsmSimulator.host,
hsmPort = hsmSimulator.port,
trustStoreFile = rootKeyStoreFile,
trustStorePassword = TRUSTSTORE_PASSWORD,
userConfigs = userConfigs,
crl = crlConfg
)
}
}

View File

@ -1,95 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.common.HsmBaseTest
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.hsm.authentication.InputReader
import com.r3.corda.networkmanage.hsm.generator.AutoAuthenticator
import com.r3.corda.networkmanage.hsm.generator.certificate.run
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities
import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import org.junit.Before
import org.junit.Test
import java.security.cert.X509Certificate
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class HsmKeyGenerationTest : HsmBaseTest() {
private lateinit var inputReader: InputReader
@Before
override fun setUp() {
super.setUp()
inputReader = mock()
whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username)
whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password)
}
@Test
fun `Root and network map certificates have different namespace`() {
// when root cert is created
run(createGeneratorParameters(
keyGroup = ROOT_CERT_KEY_GROUP,
rootKeyGroup = null,
certificateType = CertificateType.ROOT_CA,
subject = ROOT_CERT_SUBJECT
))
// when network map cert is created
run(createGeneratorParameters(
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.NETWORK_MAP,
subject = NETWORK_MAP_CERT_SUBJECT
))
// when doorman cert is created
run(createGeneratorParameters(
keyGroup = DOORMAN_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.INTERMEDIATE_CA,
subject = DOORMAN_CERT_SUBJECT
))
// then root cert is persisted in the HSM
AutoAuthenticator(createProviderConfig(ROOT_CERT_KEY_GROUP), HSM_USER_CONFIGS).connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCert.issuerX500Principal, rootCert.subjectX500Principal)
}
// then network map cert is persisted in the HSM
AutoAuthenticator(createProviderConfig(NETWORK_MAP_CERT_KEY_GROUP), HSM_USER_CONFIGS)
.connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val networkMapCert = keyStore.getCertificate(CORDA_NETWORK_MAP) as X509Certificate
assertNotNull(networkMapCert)
assertEquals(CordaX500Name.parse(ROOT_CERT_SUBJECT).x500Principal, networkMapCert.issuerX500Principal)
}
// then doorman cert is persisted in the HSM
AutoAuthenticator(createProviderConfig(DOORMAN_CERT_KEY_GROUP), HSM_USER_CONFIGS)
.connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val networkMapCert = keyStore.getCertificate(CORDA_INTERMEDIATE_CA) as X509Certificate
assertNotNull(networkMapCert)
assertEquals(CordaX500Name.parse(ROOT_CERT_SUBJECT).x500Principal, networkMapCert.issuerX500Principal)
}
}
}

View File

@ -1,174 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.mock
import com.r3.corda.networkmanage.common.HsmBaseTest
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import com.r3.corda.networkmanage.hsm.generator.certificate.run
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name.Companion.parse
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import net.corda.nodeapi.internal.crypto.X509Utilities.createCertificateSigningRequest
import org.junit.Test
import java.security.GeneralSecurityException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
class HsmPermissionTest : HsmBaseTest() {
/**
* This test case scenario reflects the issue observed on 02.02.2018, when permissions user CXI_GROUP permissions
* were wrongly configured on the PROD HSM box.
*
* Key groups are as follows:
* "TEST.CORDACONNECT.ROOT"
* "TEST.CORDACONNECT.OPS.NETMAP"
* "TEST.CORDACONNECT.OPS.CERT"
*
* User CXI_GROUP configurations are as follows:
* Root cert creator: TEST.CORDACONNECT.*
* Doorman cert creator: TEST.CORDACONNECT.*
* Networkmap cert creator: TEST.CORDACONNECT.*
*
* CSR signing user CXI_GROUP is as follows:
* TEST.CORDACONNECT.OPS.CERT.*
*/
@Test
fun `HSM signing service cannot sign CSR data when HSM user CXI_GROUP permissions are wrongly configured`() {
// given certs created
givenCertificatesCreated(HSM_SUPER__USER_CONFIGS, HSM_SUPER__USER_CONFIGS, HSM_SUPER__USER_CONFIGS)
// given authenticated user
val userInput = givenHsmUserAuthenticationInput(HSM_USERNAME_OPS_CERT_)
// given HSM CSR signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val signer = HsmCsrSigner(
mock(),
hsmSigningServiceConfig.doorman!!.loadRootKeyStore(),
"",
null,
3650,
Authenticator(
provider = createProvider(
hsmSigningServiceConfig.doorman!!.keyGroup,
hsmSigningServiceConfig.keySpecifier,
hsmSigningServiceConfig.device),
inputReader = userInput)
)
// give random data to sign
val toSign = ApprovedCertificateRequestData(
"test",
createCertificateSigningRequest(
parse("O=R3Cev,L=London,C=GB").x500Principal,
"my@mail.com",
generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)))
// then
// The GeneralSecurityException is thrown by the JCE layer.
// This exception is caused by the CryptoServerException with code B0680001 - permission denied.
assertFailsWith(GeneralSecurityException::class) {
signer.sign(listOf(toSign))
}
}
/**
* This test case scenario reflects the fix for the issue observed on 02.02.2018, when permissions user CXI_GROUP permissions
* were wrongly configured on the PROD HSM box.
*
* Key groups are as follows:
* "TEST.CORDACONNECT.ROOT"
* "TEST.CORDACONNECT.OPS.NETMAP"
* "TEST.CORDACONNECT.OPS.CERT"
*
* User CXI_GROUP configurations are as follows:
* Root cert creator: TEST.CORDACONNECT.*
* Doorman cert creator: TEST.CORDACONNECT.*
* Networkmap cert creator: TEST.CORDACONNECT.*
*
* CSR signing user CXI_GROUP is as follows:
* TEST.CORDACONNECT.OPS.CERT
*/
@Test
fun `HSM signing service signs CSR data when HSM user CXI_GROUP permissions are correctly configured`() {
// given certs created
givenCertificatesCreated(HSM_SUPER__USER_CONFIGS, HSM_SUPER__USER_CONFIGS, HSM_SUPER__USER_CONFIGS)
// given authenticated user
val userInput = givenHsmUserAuthenticationInput(HSM_USERNAME_OPS_CERT)
// given HSM CSR signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val signer = HsmCsrSigner(
mock(),
hsmSigningServiceConfig.doorman!!.loadRootKeyStore(),
"trustpass",
null,
3650,
Authenticator(
provider = createProvider(
hsmSigningServiceConfig.doorman!!.keyGroup,
hsmSigningServiceConfig.keySpecifier,
hsmSigningServiceConfig.device),
inputReader = userInput)
)
// give random data to sign
val toSign = ApprovedCertificateRequestData(
"test",
createCertificateSigningRequest(
parse("O=R3Cev,L=London,C=GB").x500Principal,
"my@mail.com",
generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)))
// when
signer.sign(listOf(toSign))
// then
assertNotNull(toSign.certPath)
val certificates = toSign.certPath!!.certificates
assertEquals(3, certificates.size)
}
private fun givenCertificatesCreated(rootCertUserConfigs: List<UserAuthenticationParameters>,
doormanCertUserConfigs: List<UserAuthenticationParameters>,
netMapCertUserConfigs: List<UserAuthenticationParameters>) {
// when root cert is created
run(createGeneratorParameters(
keyGroup = ROOT_CERT_KEY_GROUP,
rootKeyGroup = null,
certificateType = CertificateType.ROOT_CA,
subject = ROOT_CERT_SUBJECT,
hsmUserConfigs = rootCertUserConfigs))
// when network map cert is created
run(createGeneratorParameters(
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.NETWORK_MAP,
subject = NETWORK_MAP_CERT_SUBJECT,
hsmUserConfigs = netMapCertUserConfigs
))
// when doorman cert is created
run(createGeneratorParameters(
keyGroup = DOORMAN_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.INTERMEDIATE_CA,
subject = DOORMAN_CERT_SUBJECT,
hsmUserConfigs = doormanCertUserConfigs
))
}
}

View File

@ -1,198 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.mock
import com.r3.corda.networkmanage.common.HsmBaseTest
import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.common.utils.initialiseSerialization
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.generator.certificate.run
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import com.r3.corda.networkmanage.hsm.signer.HsmSigner
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name.Companion.parse
import net.corda.core.internal.CertRole
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import net.corda.nodeapi.internal.crypto.X509Utilities.createCertificateSigningRequest
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.nodeapi.internal.crypto.x509
import net.corda.testing.common.internal.testNetworkParameters
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
class HsmSigningServiceTest : HsmBaseTest() {
@Before
override fun setUp() {
super.setUp()
loadOrCreateKeyStore(rootKeyStoreFile, TRUSTSTORE_PASSWORD)
}
@Test
fun `HSM signing service can sign node CSR data`() {
setupCertificates()
// given authenticated user
val userInput = givenHsmUserAuthenticationInput()
// given HSM CSR signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val doormanCertificateConfig = hsmSigningServiceConfig.doorman!!
val signer = HsmCsrSigner(
mock(),
doormanCertificateConfig.loadRootKeyStore(),
"",
null,
3650,
Authenticator(
provider = createProvider(
doormanCertificateConfig.keyGroup,
hsmSigningServiceConfig.keySpecifier,
hsmSigningServiceConfig.device),
inputReader = userInput)
)
// give random data to sign
val toSign = ApprovedCertificateRequestData(
"test",
createCertificateSigningRequest(
parse("O=R3Cev,L=London,C=GB").x500Principal,
"my@mail.com",
generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)))
// when
signer.sign(listOf(toSign))
// then
assertNotNull(toSign.certPath)
val certificates = toSign.certPath!!.certificates
assertEquals(3, certificates.size)
// Is a CA
assertNotEquals(-1, certificates.first().x509.basicConstraints)
assertEquals(CertRole.NODE_CA, CertRole.extract(certificates.first().x509))
}
@Test
fun `HSM signing service can sign service identity CSR data`() {
setupCertificates()
// given authenticated user
val userInput = givenHsmUserAuthenticationInput()
// given HSM CSR signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val doormanCertificateConfig = hsmSigningServiceConfig.doorman!!
val signer = HsmCsrSigner(
mock(),
doormanCertificateConfig.loadRootKeyStore(),
"",
null,
3650,
Authenticator(
provider = createProvider(
doormanCertificateConfig.keyGroup,
hsmSigningServiceConfig.keySpecifier,
hsmSigningServiceConfig.device),
inputReader = userInput)
)
// give random data to sign
val toSign = ApprovedCertificateRequestData(
"test",
createCertificateSigningRequest(
parse("O=R3Cev,L=London,C=GB").x500Principal,
"my@mail.com",
generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME),
certRole = CertRole.SERVICE_IDENTITY))
// when
signer.sign(listOf(toSign))
// then
assertNotNull(toSign.certPath)
val certificates = toSign.certPath!!.certificates
assertEquals(3, certificates.size)
// Not a CA
assertEquals(-1, certificates.first().x509.basicConstraints)
assertEquals(CertRole.SERVICE_IDENTITY, CertRole.extract(certificates.first().x509))
}
@Test
fun `HSM signing service can sign and serialize network map data to the Doorman DB`() {
setupCertificates()
// given authenticated user
val userInput = givenHsmUserAuthenticationInput()
// given HSM network map signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig(null, createNetworkMapCertificateConfig())
val networkMapCertificateConfig = hsmSigningServiceConfig.networkMap!!
val hsmDataSigner = HsmSigner(Authenticator(
provider = createProvider(
networkMapCertificateConfig.keyGroup,
hsmSigningServiceConfig.keySpecifier,
hsmSigningServiceConfig.device),
inputReader = userInput),
keyName = CORDA_NETWORK_MAP)
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties())
val networkMapStorage = PersistentNetworkMapStorage(database)
// given network map parameters
val networkMapParameters = testNetworkParameters(emptyList())
val networkMapSigner = NetworkMapSigner(networkMapStorage, hsmDataSigner)
// when
initialiseSerialization()
networkMapStorage.saveNetworkParameters(networkMapParameters, hsmDataSigner.signBytes(networkMapParameters.serialize().bytes))
networkMapSigner.signNetworkMaps()
// then
val persistedNetworkMap = networkMapStorage.getNetworkMaps().publicNetworkMap!!.toSignedNetworkMap().verified()
assertEquals(networkMapParameters.serialize().hash, persistedNetworkMap.networkParameterHash)
assertThat(persistedNetworkMap.nodeInfoHashes).isEmpty()
}
private fun setupCertificates() {
// when root cert is created
run(createGeneratorParameters(
keyGroup = ROOT_CERT_KEY_GROUP,
rootKeyGroup = null,
certificateType = CertificateType.ROOT_CA,
subject = ROOT_CERT_SUBJECT
))
// when network map cert is created
run(createGeneratorParameters(
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.NETWORK_MAP,
subject = NETWORK_MAP_CERT_SUBJECT
))
// when doorman cert is created
run(createGeneratorParameters(
keyGroup = DOORMAN_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.INTERMEDIATE_CA,
subject = DOORMAN_CERT_SUBJECT
))
}
}

View File

@ -1,189 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.*
import com.r3.corda.networkmanage.common.*
import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.doorman.CertificateRevocationConfig
import com.r3.corda.networkmanage.doorman.DoormanConfig
import com.r3.corda.networkmanage.doorman.NetworkManagementServer
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateSigningRequestStorage
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.seconds
import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.rigorousMock
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.net.URL
import java.nio.file.Path
import java.security.cert.X509Certificate
import java.util.*
import javax.persistence.PersistenceException
import kotlin.concurrent.scheduleAtFixedRate
class SigningServiceIntegrationTest : HsmBaseTest() {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val portAllocation = PortAllocation.Incremental(10000)
private val serverAddress = portAllocation.nextHostAndPort()
private lateinit var timer: Timer
private lateinit var rootCaCert: X509Certificate
private lateinit var intermediateCa: CertificateAndKeyPair
private val timeoutMillis = 5.seconds.toMillis()
private lateinit var dbName: String
private val doormanConfig: DoormanConfig get() = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null)
private lateinit var revocationConfig: CertificateRevocationConfig
private fun createCertificateRevocationConfig(emptyCrlPath: Path, caCrlPath: Path): CertificateRevocationConfig {
return CertificateRevocationConfig(
approveAll = true,
jira = null,
approveInterval = timeoutMillis,
crlCacheTimeout = timeoutMillis,
localSigning = CertificateRevocationConfig.LocalSigning(
crlEndpoint = getNodeCrlEndpoint(serverAddress),
crlUpdateInterval = timeoutMillis),
emptyCrlPath = emptyCrlPath,
caCrlPath = caCrlPath)
}
@Before
override fun setUp() {
super.setUp()
dbName = random63BitValue().toString()
timer = Timer()
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
rootCaCert = rootCa.certificate
this.intermediateCa = intermediateCa
val (caCrlPath, emptyCrlPath) = generateEmptyCrls(tempFolder, rootCa, getCaCrlEndpoint(serverAddress), getEmptyCrlEndpoint(serverAddress))
revocationConfig = createCertificateRevocationConfig(emptyCrlPath, caCrlPath)
}
@After
override fun tearDown() {
super.tearDown()
timer.cancel()
}
private fun givenSignerSigningAllRequests(storage: SignedCertificateSigningRequestStorage): HsmCsrSigner {
// Mock signing logic but keep certificate persistence
return mock {
on { sign(any()) }.then {
val approvedRequests: List<ApprovedCertificateRequestData> = uncheckedCast(it.arguments[0])
for (approvedRequest in approvedRequests) {
JcaPKCS10CertificationRequest(approvedRequest.request).run {
val nodeCa = createDevNodeCa(intermediateCa, CordaX500Name.parse(subject.toString()))
approvedRequest.certPath = X509Utilities.buildCertPath(nodeCa.certificate, intermediateCa.certificate, rootCaCert)
}
}
storage.store(approvedRequests, "TEST")
}
}
}
@Test
fun `Signing service signs approved CSRs`() {
//Start doorman server
NetworkManagementServer(makeTestDataSourceProperties(), makeTestDatabaseProperties(), doormanConfig, revocationConfig).use { server ->
server.start(
hostAndPort = serverAddress,
csrCertPathAndKey = null,
startNetworkMap = null)
val doormanHostAndPort = server.hostAndPort
// Start Corda network registration.
val config = createConfig().also {
doReturn(ALICE_NAME).whenever(it).myLegalName
doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL
}
val signingServiceStorage = DBSignedCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties()))
val hsmSigner = givenSignerSigningAllRequests(signingServiceStorage)
// Poll the database for approved requests
timer.scheduleAtFixedRate(0, 1.seconds.toMillis()) {
// The purpose of this tests is to validate the communication between this service and Doorman
// by the means of data in the shared database.
// Therefore the HSM interaction logic is mocked here.
try {
val approved = signingServiceStorage.getApprovedRequests()
if (approved.isNotEmpty()) {
hsmSigner.sign(approved)
timer.cancel()
}
} catch (exception: PersistenceException) {
// It may happen that Doorman DB is not created at the moment when the signing service polls it.
// This is due to the fact that schema is initialized at the time first hibernate session is established.
// Since Doorman does this at the time the first CSR arrives, which in turn happens after signing service
// startup, the very first iteration of the signing service polling fails with
// [org.hibernate.tool.schema.spi.SchemaManagementException] being thrown as the schema is missing.
}
}
config.certificatesDirectory.createDirectories()
val networkTrustStorePath = config.certificatesDirectory / "network-root-truststore.jks"
val networkTrustStorePassword = "network-trust-password"
val networkTrustStore = X509KeyStore.fromFile(networkTrustStorePath, networkTrustStorePassword, createNew = true)
networkTrustStore.update {
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCaCert)
}
val trustStore = X509KeyStore.fromFile(config.trustStoreFile, config.trustStorePassword, createNew = true)
val nodeKeyStore = X509KeyStore.fromFile(config.nodeKeystore, config.keyStorePassword, createNew = true)
val sslKeyStore = X509KeyStore.fromFile(config.sslKeystore, config.keyStorePassword, createNew = true)
config.also {
doReturn(trustStore).whenever(it).loadTrustStore(any())
doReturn(nodeKeyStore).whenever(it).loadNodeKeyStore(any())
doReturn(sslKeyStore).whenever(it).loadSslKeyStore(any())
}
val regConfig = NodeRegistrationOption(networkTrustStorePath, networkTrustStorePassword)
NodeRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!), regConfig).buildKeystore()
verify(hsmSigner).sign(any())
}
}
private fun createConfig(): NodeConfiguration {
return rigorousMock<NodeConfiguration>().also {
doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory
doReturn(it.baseDirectory / "certificates").whenever(it).certificatesDirectory
doReturn(it.certificatesDirectory / "truststore.jks").whenever(it).trustStoreFile
doReturn(it.certificatesDirectory / "nodekeystore.jks").whenever(it).nodeKeystore
doReturn(it.certificatesDirectory / "sslkeystore.jks").whenever(it).sslKeystore
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn("iTest@R3.com").whenever(it).emailAddress
}
}
}

View File

@ -1,23 +0,0 @@
package com.r3.corda.networkmanage.common.configuration
import com.r3.corda.networkmanage.common.utils.ArgsParser
import joptsimple.OptionSet
import joptsimple.util.PathConverter
import joptsimple.util.PathProperties
import java.nio.file.Path
/**
* Parses key generator command line options.
*/
class ConfigFilePathArgsParser : ArgsParser<Path>() {
private val configFileArg = optionParser
.accepts("config-file", "The path to the config file")
.withRequiredArg()
.required()
.describedAs("filepath")
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
override fun parse(optionSet: OptionSet): Path {
return optionSet.valueOf(configFileArg).toAbsolutePath()
}
}

View File

@ -1,17 +0,0 @@
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage.Companion.DOORMAN_SIGNATURE
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
/**
* This storage automatically approves all created requests.
*/
class ApproveAllCertificateRevocationRequestStorage(private val delegate: CertificateRevocationRequestStorage) : CertificateRevocationRequestStorage by delegate {
override fun saveRevocationRequest(request: CertificateRevocationRequest): String {
val requestId = delegate.saveRevocationRequest(request)
delegate.markRequestTicketCreated(requestId)
approveRevocationRequest(requestId, DOORMAN_SIGNATURE)
return requestId
}
}

View File

@ -1,15 +0,0 @@
package com.r3.corda.networkmanage.common.persistence
import org.bouncycastle.pkcs.PKCS10CertificationRequest
/**
* This storage automatically approves all created requests.
*/
class ApproveAllCertificateSigningRequestStorage(private val delegate: CertificateSigningRequestStorage) : CertificateSigningRequestStorage by delegate {
override fun saveRequest(request: PKCS10CertificationRequest): String {
val requestId = delegate.saveRequest(request)
delegate.markRequestTicketCreated(requestId)
approveRequest(requestId, CertificateSigningRequestStorage.DOORMAN_SIGNATURE)
return requestId
}
}

View File

@ -1,40 +0,0 @@
package com.r3.corda.networkmanage.common.persistence
import java.security.cert.X509CRL
import java.time.Instant
/**
* Interface for managing certificate revocation list persistence
*/
interface CertificateRevocationListStorage {
companion object {
val DOORMAN_SIGNATURE = "Doorman-Crl-Signer"
}
/**
* Retrieves the latest certificate revocation list.
*
* @param crlIssuer CRL issuer CA type.
* @return latest revocation list.
*/
fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL?
/**
* Persists a new revocation list. Upon saving, statuses
* of the approved revocation requests will automatically change to [RequestStatus.DONE].
*
* @param crl signed instance of the certificate revocation list. It will be serialized and stored as part of a
* database entity.
* @param crlIssuer CRL issuer CA type.
* @param signedBy who signed this CRL.
* @param revokedAt revocation time.
*/
fun saveCertificateRevocationList(crl: X509CRL, crlIssuer: CrlIssuer, signedBy: String, revokedAt: Instant)
}
/**
* There are 2 CAs that issue CRLs (i.e. Root CA and Doorman CA).
*/
enum class CrlIssuer {
ROOT, DOORMAN
}

View File

@ -1,85 +0,0 @@
package com.r3.corda.networkmanage.common.persistence
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
import java.math.BigInteger
import java.security.cert.CRLReason
import java.security.cert.X509Certificate
import java.time.Instant
/**
* This data class is intended to be used internally certificate revocation request service.
*/
@CordaSerializable
data class CertificateRevocationRequestData(val requestId: String, // This is a uniquely generated string
val certificateSigningRequestId: String,
val certificate: X509Certificate,
val certificateSerialNumber: BigInteger,
val modifiedAt: Instant,
val legalName: CordaX500Name,
val status: RequestStatus,
val reason: CRLReason,
val reporter: String) // Username of the reporter
/**
* Interface for managing certificate revocation requests persistence
*/
interface CertificateRevocationRequestStorage {
companion object {
val DOORMAN_SIGNATURE = "Doorman-Crr-Signer"
}
/**
* Creates a new revocation request for the given [certificateSerialNumber].
* The newly created revocation request has the [RequestStatus.NEW] status.
* If the revocation request with the [certificateSerialNumber] already exists and has status
* [RequestStatus.NEW], [RequestStatus.APPROVED] or [RequestStatus.REVOKED]
* then nothing is persisted and the existing revocation request identifier is returned.
*
* @param request certificate revocation request to be stored.
*
* @return identifier of the newly created (or existing) revocation request.
*/
fun saveRevocationRequest(request: CertificateRevocationRequest): String
/**
* Retrieves the revocation request with the given [requestId]
*
* @param requestId revocation request identifier
*
* @return CertificateRevocationRequest matching the specified identifier. Or null if it doesn't exist.
*/
fun getRevocationRequest(requestId: String): CertificateRevocationRequestData?
/**
* Retrieves all the revocation requests with the specified revocation request status.
*
* @param revocationStatus revocation request status of the returned revocation requests.
*
* @return list of certificate revocation requests that match the revocation request status.
*/
fun getRevocationRequests(revocationStatus: RequestStatus): List<CertificateRevocationRequestData>
/**
* Changes the revocation request status to [RequestStatus.APPROVED].
*
* @param requestId revocation request identifier
* @param approvedBy who is approving it
*/
fun approveRevocationRequest(requestId: String, approvedBy: String)
/**
* Changes the revocation request status to [RequestStatus.REJECTED].
*
* @param requestId revocation request identifier
* @param rejectedBy who is rejecting it
* @param reason description of the reason of this rejection.
*/
fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String?)
/**
* Persist the fact that a ticket has been created for the given [requestId].
*/
fun markRequestTicketCreated(requestId: String)
}

View File

@ -1,133 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.PublicKey
import java.security.cert.CertPath
data class CertificateData(val certStatus: CertificateStatus, val certPath: CertPath)
data class CertificateSigningRequest(val requestId: String,
val legalName: CordaX500Name?,
val publicKeyHash: SecureHash,
val status: RequestStatus,
val request: PKCS10CertificationRequest,
val remark: String?,
val modifiedBy: String,
val certData: CertificateData?)
/**
* Provide certificate signing request storage for the certificate signing server.
*/
interface CertificateSigningRequestStorage {
companion object {
val DOORMAN_SIGNATURE = "Doorman-Csr-Signer"
}
/**
* Persist [PKCS10CertificationRequest] in storage for further approval if it's a valid request.
* If not then it will be automatically rejected and not subject to any approval process.
* In both cases a randomly generated request ID is returned.
* @param request request to be stored
*/
fun saveRequest(request: PKCS10CertificationRequest): String
/**
* Retrieve certificate singing request using [requestId].
* @return certificate signing request or null if the request does not exist
*/
fun getRequest(requestId: String): CertificateSigningRequest?
/**
* Retrieve list of certificate signing request based on the [RequestStatus].
*/
fun getRequests(requestStatus: RequestStatus): List<CertificateSigningRequest>
/**
* Persist the fact that a ticket has been created for the given [requestId].
*/
fun markRequestTicketCreated(requestId: String)
/**
* Approve the given request if it has not already been approved. Otherwise do nothing.
* @param requestId id of the certificate signing request
* @param approvedBy authority (its identifier) approving this request.
*/
// TODO: Merge status changing methods.
fun approveRequest(requestId: String, approvedBy: String)
/**
* Reject the given request using the given reason.
* @param requestId id of the certificate signing request
* @param rejectedBy authority (its identifier) rejecting this request.
* @param rejectReason brief description of the rejection reason
*/
fun rejectRequest(requestId: String, rejectedBy: String, rejectReason: String?)
/**
* Store certificate path with [requestId], this will store the encoded [CertPath] and transit request status to [RequestStatus.DONE].
* @param requestId id of the certificate signing request
* @param certPath chain of certificates starting with the one generated in response to the CSR up to the root.
* @param signedBy authority (its identifier) signing this request.
* @throws IllegalArgumentException if request is not found or not in Approved state.
*/
fun putCertificatePath(requestId: String, certPath: CertPath, signedBy: String)
/**
* Retrieves the certificate path for the given public key hash if such exists and its certificate is considered to be valid.
*
* @param publicKey public key corresponding to the certificate being searched.
* @return certificate path for the given public key hash or null if such certificate does not exist or is not valid.
*/
fun getValidCertificatePath(publicKey: PublicKey): CertPath?
}
sealed class CertificateResponse {
object NotReady : CertificateResponse()
data class Ready(val certificatePath: CertPath) : CertificateResponse()
data class Unauthorised(val message: String) : CertificateResponse()
}
@CordaSerializable
enum class RequestStatus {
/**
* The request has been received, this is the initial state in which a request has been created.
*/
NEW,
/**
* A ticket has been created but has not yet been approved nor rejected.
*/
TICKET_CREATED,
/**
* The request has been approved, but not yet signed.
*/
APPROVED,
/**
* The request has been rejected, this is a terminal state, once a request gets in this state it won't change anymore.
*/
REJECTED,
/**
* The request has been successfully processed, this is a terminal state, once a request gets in this state it won't change anymore.
*/
DONE
}
enum class CertificateStatus {
VALID, SUSPENDED, REVOKED
}

View File

@ -1,89 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.*
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NetworkParameters
import net.corda.nodeapi.internal.network.NetworkMapAndSigned
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import java.time.Instant
/**
* Data access object interface for NetworkMap persistence layer
*/
// TODO This storage abstraction needs some thought. Some of the methods clearly don't make sense e.g. setParametersUpdateStatus.
// TODO: We should avoid exposing entity objects.
// The NetworkMapSignerTest uses a mock of this which means we need to provide methods for every trivial db operation.
interface NetworkMapStorage {
/**
* Returns the network maps containing public network map and private network maps.
*/
fun getNetworkMaps(): NetworkMaps
/**
* Persist the new network map for provided network ID, replacing any existing network map.
* The map will be stored as public network map if [networkId] = null.
*/
fun saveNewNetworkMap(networkId: String? = null, networkMapAndSigned: NetworkMapAndSigned)
/**
* Retrieves node info hashes for both public and private networks where [NodeInfoEntity.isCurrent] is true and the certificate status is [CertificateStatus.VALID],
* and that were published less than eventHorizon ago.
* Nodes should have declared that they are using correct set of parameters.
*/
fun getNodeInfoHashes(): NodeInfoHashes
/**
* Retrieve the signed with certificate network parameters by their hash. The hash is that of the underlying
* [NetworkParameters] object and not the [SignedNetworkParameters] object that's returned.
* @return signed network parameters corresponding to the given hash or null if it does not exist (parameters don't exist or they haven't been signed yet)
*/
fun getSignedNetworkParameters(hash: SecureHash): SignedNetworkParameters?
/**
* Persists given network parameters with signature if provided.
* @return The newly inserted [NetworkParametersEntity]
*/
fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): NetworkParametersEntity
/**
* Save new parameters update information with corresponding network parameters. Only one parameters update entity
* can be NEW or FLAG_DAY at any time - if one exists it will be cancelled.
*/
fun saveNewParametersUpdate(networkParameters: NetworkParameters, description: String, updateDeadline: Instant)
/**
* Retrieves the latest (i.e. most recently inserted) network parameters entity
* Note that they may not have been signed up yet.
* @return latest network parameters entity
*/
fun getLatestNetworkParameters(): NetworkParametersEntity?
/** Returns the single new or flag day parameters update, or null if there isn't one. */
fun getCurrentParametersUpdate(): ParametersUpdateEntity?
fun setParametersUpdateStatus(update: ParametersUpdateEntity, newStatus: UpdateStatus)
/**
* Perform the switch of parameters on the flagDay.
* 1. Change status of ParametersUpdateEntity to [UpdateStatus.APPLIED]
* 2. Mark all the node infos that didn't accept the update as not current (so they won't be advertised in the network map)
*/
fun switchFlagDay(update: ParametersUpdateEntity)
}
data class NetworkMaps(val publicNetworkMap: NetworkMapEntity?, val privateNetworkMap: Map<String, NetworkMapEntity>) {
val allNodeInfoHashes: Set<SecureHash> = privateNetworkMap.flatMap { it.value.networkMap.nodeInfoHashes }.toSet() + (publicNetworkMap?.networkMap?.nodeInfoHashes ?: emptySet())
}
data class NodeInfoHashes(val publicNodeInfoHashes: List<SecureHash>, val privateNodeInfoHashes: Map<String, List<SecureHash>>)

View File

@ -1,60 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity
import net.corda.core.crypto.SecureHash
import net.corda.core.node.NodeInfo
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import java.security.PublicKey
import java.security.cert.CertPath
/**
* Data access object interface for NetworkMap/NodeInfo persistence layer
*/
interface NodeInfoStorage {
/**
* Retrieve node certificate path using the node public key hash.
* @return [CertPath] or null if the public key is not registered with the Doorman.
*/
fun getCertificatePath(publicKeyHash: SecureHash): CertPath?
/**
* Retrieve node info together with its signature using nodeInfo's hash
* @return [NodeInfo] or null if the node info is not registered.
*/
fun getNodeInfo(nodeInfoHash: SecureHash): SignedNodeInfo?
/**
* Returns the parameters update the node has accepted or null if couldn't find node info with given hash or
* there is no information on accepted parameters update stored for this entity.
*/
fun getAcceptedParametersUpdate(nodeInfoHash: SecureHash): ParametersUpdateEntity?
/**
* The [nodeInfoAndSigned] is keyed by the public key, old node info with the same public key will be replaced by the new node info.
* If republishing of the same nodeInfo happens, then we will record the time it was republished in the database.
* Based on that information we can remove unresponsive nodes from network (event horizon is the parameter that tells how
* long node can be down before it gets removed). If the nodes becomes active again, it will enter back to the network map
* after republishing its [NodeInfo].
* @param nodeInfoAndSigned signed node info data to be stored
* @return hash for the newly created node info entry
*/
fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash
/**
* Store information about latest accepted [NetworkParameters] hash.
* @param publicKey Public key that accepted network parameters. This public key should belong to [NodeInfo]
* @param acceptedParametersHash Hash of latest accepted network parameters.
*/
fun ackNodeInfoParametersUpdate(publicKey: PublicKey, acceptedParametersHash: SecureHash)
}

View File

@ -1,80 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.*
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import net.corda.core.schemas.MappedSchema
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import net.corda.nodeapi.internal.persistence.SchemaMigration
import org.hibernate.Session
import org.hibernate.query.Query
import java.util.*
import javax.persistence.LockModeType
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.Path
import javax.persistence.criteria.Predicate
inline fun <reified T> Session.fromQuery(query: String): Query<T> {
return createQuery("from ${T::class.java.name} $query", T::class.java)
}
inline fun <reified T> DatabaseTransaction.uniqueEntityWhere(predicate: (CriteriaBuilder, Path<T>) -> Predicate): T? {
return entitiesWhere(predicate).setMaxResults(1).uniqueResult()
}
inline fun <reified T> DatabaseTransaction.entitiesWhere(predicate: (CriteriaBuilder, Path<T>) -> Predicate): Query<T> {
val builder = session.criteriaBuilder
val criteriaQuery = builder.createQuery(T::class.java)
val query = criteriaQuery.from(T::class.java).run {
criteriaQuery.where(predicate(builder, this))
}
return session.createQuery(query).setLockMode(LockModeType.PESSIMISTIC_WRITE)
}
inline fun <reified T> DatabaseTransaction.deleteEntity(predicate: (CriteriaBuilder, Path<T>) -> Predicate): Int {
val builder = session.criteriaBuilder
val criteriaDelete = builder.createCriteriaDelete(T::class.java)
val delete = criteriaDelete.from(T::class.java).run {
criteriaDelete.where(predicate(builder, this))
}
return session.createQuery(delete).executeUpdate()
}
fun configureDatabase(dataSourceProperties: Properties,
databaseConfig: DatabaseConfig = DatabaseConfig()): CordaPersistence {
val config = HikariConfig(dataSourceProperties)
val dataSource = HikariDataSource(config)
val schemas = setOf(NetworkManagementSchemaServices.SchemaV1)
SchemaMigration(schemas, dataSource, true, databaseConfig).nodeStartup()
return CordaPersistence(dataSource, databaseConfig, schemas, config.dataSourceProperties.getProperty("url", ""), emptyList())
}
sealed class NetworkManagementSchemaServices {
object SchemaV1 : MappedSchema(schemaFamily = NetworkManagementSchemaServices::class.java, version = 1,
mappedTypes = listOf(
CertificateSigningRequestEntity::class.java,
CertificateDataEntity::class.java,
CertificateRevocationRequestEntity::class.java,
PrivateNetworkEntity::class.java,
CertificateRevocationListEntity::class.java,
NodeInfoEntity::class.java,
NetworkParametersEntity::class.java,
NetworkMapEntity::class.java,
ParametersUpdateEntity::class.java)) {
override val migrationResource = "network-manager.changelog-master"
}
}

View File

@ -1,60 +0,0 @@
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.CertificateDataEntity
import com.r3.corda.networkmanage.common.persistence.entity.CertificateRevocationListEntity
import com.r3.corda.networkmanage.common.persistence.entity.CertificateRevocationRequestEntity
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import java.math.BigInteger
import java.security.cert.X509CRL
import java.time.Instant
class PersistentCertificateRevocationListStorage(private val database: CordaPersistence) : CertificateRevocationListStorage {
override fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL? {
return database.transaction {
val builder = session.criteriaBuilder
val query = builder.createQuery(CertificateRevocationListEntity::class.java).run {
from(CertificateRevocationListEntity::class.java).run {
orderBy(builder.desc(get<String>(CertificateRevocationListEntity::id.name)))
where(builder.equal(get<CrlIssuer>(CertificateRevocationListEntity::crlIssuer.name), crlIssuer))
}
}
// We just want the last signed entry
session.createQuery(query).setMaxResults(1).uniqueResult()?.crl
}
}
override fun saveCertificateRevocationList(crl: X509CRL, crlIssuer: CrlIssuer, signedBy: String, revokedAt: Instant) {
database.transaction {
crl.revokedCertificates?.forEach {
revokeCertificate(it.serialNumber, revokedAt, this)
}
session.save(CertificateRevocationListEntity(
crl = crl,
crlIssuer = crlIssuer,
signedBy = signedBy,
modifiedAt = Instant.now()
))
}
}
private fun revokeCertificate(certificateSerialNumber: BigInteger, time: Instant, transaction: DatabaseTransaction) {
val revocation = transaction.uniqueEntityWhere<CertificateRevocationRequestEntity> { builder, path ->
builder.and(
builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber),
builder.notEqual(path.get<RequestStatus>(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED)
)
}
revocation ?: throw IllegalStateException("The certificate revocation request for $certificateSerialNumber does not exist")
check(revocation.status in arrayOf(RequestStatus.APPROVED, RequestStatus.DONE)) {
"The certificate revocation request for $certificateSerialNumber has unexpected status of ${revocation.status}"
}
val session = transaction.session
val certificateData = session.merge(revocation.certificateData.copy(certificateStatus = CertificateStatus.REVOKED)) as CertificateDataEntity
session.merge(revocation.copy(
status = RequestStatus.DONE,
modifiedAt = time,
certificateData = certificateData
))
}
}

View File

@ -1,237 +0,0 @@
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.CertificateDataEntity
import com.r3.corda.networkmanage.common.persistence.entity.CertificateRevocationRequestEntity
import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRequestEntity
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
import java.math.BigInteger
import java.security.cert.CRLReason
import java.security.cert.X509Certificate
import java.time.Instant
class PersistentCertificateRevocationRequestStorage(private val database: CordaPersistence) : CertificateRevocationRequestStorage {
private companion object {
val ALLOWED_REASONS = arrayOf(
CRLReason.KEY_COMPROMISE,
CRLReason.AFFILIATION_CHANGED,
CRLReason.CA_COMPROMISE,
CRLReason.CESSATION_OF_OPERATION,
CRLReason.PRIVILEGE_WITHDRAWN,
CRLReason.SUPERSEDED,
CRLReason.UNSPECIFIED
)
val logger = contextLogger()
}
override fun saveRevocationRequest(request: CertificateRevocationRequest): String {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
// Get matching CSR
validate(request)
val csr = requireNotNull(retrieveCsr(request.certificateSerialNumber, request.csrRequestId, request.legalName)) {
"No CSR matching the given criteria was found"
}
// Check if there is an entry for the given certificate serial number
val revocation = uniqueEntityWhere<CertificateRevocationRequestEntity> { builder, path ->
val serialNumberEqual = builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), request.certificateSerialNumber)
val statusNotEqualRejected = builder.notEqual(path.get<RequestStatus>(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED)
builder.and(serialNumberEqual, statusNotEqualRejected)
}
if (revocation != null) {
revocation.requestId
} else {
val certificateData = csr.certificateData!!
requireNotNull(certificateData) { "The certificate with the given serial number cannot be found." }
val requestId = SecureHash.randomSHA256().toString()
session.save(CertificateRevocationRequestEntity(
certificateSerialNumber = certificateData.certificateSerialNumber,
revocationReason = request.reason,
requestId = requestId,
modifiedBy = request.reporter,
certificateData = certificateData,
reporter = request.reporter,
legalName = certificateData.legalName
))
requestId
}
}
}
private fun validate(request: CertificateRevocationRequest) {
require(request.reason in ALLOWED_REASONS) { "The given revocation reason is not allowed." }
}
private fun DatabaseTransaction.retrieveCsr(certificateSerialNumber: BigInteger?, csrRequestId: String?, legalName: CordaX500Name?): CertificateSigningRequestEntity? {
val csr = if (csrRequestId != null) {
uniqueEntityWhere<CertificateSigningRequestEntity> { builder, path ->
builder.and(
builder.equal(path.get<String>(CertificateSigningRequestEntity::requestId.name), csrRequestId),
builder.notEqual(path.get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED),
builder.equal(path
.get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID)
)
}
} else if (legalName != null) {
uniqueEntityWhere<CertificateSigningRequestEntity> { builder, path ->
builder.and(
builder.equal(path.get<String>(CertificateSigningRequestEntity::legalName.name), legalName.toString()),
builder.notEqual(path.get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED),
builder.equal(path
.get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID)
)
}
} else {
val certificateData = uniqueEntityWhere<CertificateDataEntity> { builder, path ->
builder.and(
builder.equal(path.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID),
builder.equal(path.get<String>(CertificateDataEntity::certificateSerialNumber.name), certificateSerialNumber)
)
}
certificateData?.certificateSigningRequest
}
return csr?.let {
validateCsrConsistency(certificateSerialNumber, csrRequestId, legalName, it)
}
}
private fun validateCsrConsistency(certificateSerialNumber: BigInteger?,
csrRequestId: String?,
legalName: CordaX500Name?,
result: CertificateSigningRequestEntity): CertificateSigningRequestEntity {
val certData = result.certificateData!!
require(legalName == null || result.legalName == legalName) {
"The legal name does not match."
}
require(csrRequestId == null || result.requestId == csrRequestId) {
"The CSR request ID does not match."
}
require(certificateSerialNumber == null || certData.certificateSerialNumber == certificateSerialNumber) {
"The certificate serial number does not match."
}
return result
}
override fun getRevocationRequest(requestId: String): CertificateRevocationRequestData? {
return database.transaction {
getRevocationRequestEntity(requestId)?.toCertificateRevocationRequestData()
}
}
override fun getRevocationRequests(revocationStatus: RequestStatus): List<CertificateRevocationRequestData> {
return database.transaction {
val builder = session.criteriaBuilder
val query = builder.createQuery(CertificateRevocationRequestEntity::class.java).run {
from(CertificateRevocationRequestEntity::class.java).run {
where(builder.equal(get<RequestStatus>(CertificateRevocationRequestEntity::status.name), revocationStatus))
}
}
session.createQuery(query).resultList.map { it.toCertificateRevocationRequestData() }
}
}
override fun approveRevocationRequest(requestId: String, approvedBy: String) {
database.transaction {
val revocation = getRevocationRequestEntity(requestId)
if (revocation == null) {
throw NoSuchElementException("Error while approving! Certificate revocation id=$id does not exist")
} else {
when (revocation.status) {
RequestStatus.TICKET_CREATED -> {
session.merge(revocation.copy(
status = RequestStatus.APPROVED,
modifiedAt = Instant.now(),
modifiedBy = approvedBy
))
logger.debug("`request id` = $requestId marked as APPROVED")
}
else -> {
logger.warn("`request id` = $requestId cannot be marked as APPROVED. Its current status is ${revocation.status}")
return@transaction
}
}
}
}
}
override fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String?) {
database.transaction {
val revocation = getRevocationRequestEntity(requestId)
if (revocation == null) {
throw NoSuchElementException("Error while rejecting! Certificate revocation id=$id does not exist")
} else {
when (revocation.status) {
RequestStatus.TICKET_CREATED -> {
session.merge(revocation.copy(
status = RequestStatus.REJECTED,
modifiedAt = Instant.now(),
modifiedBy = rejectedBy,
remark = reason
))
logger.debug("`request id` = $requestId marked as REJECTED")
}
else -> {
logger.warn("`request id` = $requestId cannot be marked as REJECTED. Its current status is ${revocation.status}")
return@transaction
}
}
}
}
}
override fun markRequestTicketCreated(requestId: String) {
database.transaction {
val revocation = getRevocationRequestEntity(requestId)
if (revocation == null) {
throw NoSuchElementException("Error while marking the request as ticket created! Certificate revocation id=$id does not exist")
} else {
when (revocation.status) {
RequestStatus.NEW -> {
session.merge(revocation.copy(
modifiedAt = Instant.now(),
status = RequestStatus.TICKET_CREATED
))
logger.debug("`request id` = $requestId marked as TICKED_CREATED")
}
else -> {
logger.warn("`request id` = $requestId cannot be marked as TICKED_CREATED. Its current status is ${revocation.status}")
return@transaction
}
}
}
}
}
private fun getRevocationRequestEntity(requestId: String, status: RequestStatus? = null): CertificateRevocationRequestEntity? {
return database.transaction {
uniqueEntityWhere { builder, path ->
val idEqual = builder.equal(path.get<String>(CertificateRevocationRequestEntity::requestId.name), requestId)
if (status == null) {
idEqual
} else {
builder.and(idEqual, builder.equal(path.get<RequestStatus>(CertificateRevocationRequestEntity::status.name), status))
}
}
}
}
private fun CertificateRevocationRequestEntity.toCertificateRevocationRequestData(): CertificateRevocationRequestData {
return CertificateRevocationRequestData(
requestId,
certificateData.certificateSigningRequest.requestId,
certificateData.certPath.certificates.first() as X509Certificate,
certificateSerialNumber,
modifiedAt,
legalName,
status,
revocationReason,
reporter
)
}
}

View File

@ -1,222 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.CertificateDataEntity
import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRequestEntity
import com.r3.corda.networkmanage.common.utils.getCertRole
import net.corda.core.crypto.Crypto.toSupportedPublicKey
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole
import net.corda.core.internal.hash
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.PublicKey
import java.security.cert.CertPath
import java.time.Instant
import javax.persistence.LockModeType
import javax.security.auth.x500.X500Principal
/**
* Database implementation of the [CertificateSigningRequestStorage] interface.
*/
class PersistentCertificateSigningRequestStorage(private val database: CordaPersistence) : CertificateSigningRequestStorage {
companion object {
// TODO: make this configurable?
private val allowedCertRoles = setOf(CertRole.NODE_CA, CertRole.SERVICE_IDENTITY)
}
override fun putCertificatePath(requestId: String, certPath: CertPath, signedBy: String) {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val request = requireNotNull(uniqueEntityWhere<CertificateSigningRequestEntity> { builder, path ->
val requestIdEq = builder.equal(path.get<String>(CertificateSigningRequestEntity::requestId.name), requestId)
val statusEq = builder.equal(path.get<String>(CertificateSigningRequestEntity::status.name), RequestStatus.APPROVED)
builder.and(requestIdEq, statusEq)
}) { "Cannot retrieve 'APPROVED' certificate signing request for request id: $requestId" }
val certificateSigningRequest = request.copy(
modifiedBy = signedBy,
modifiedAt = Instant.now(),
status = RequestStatus.DONE)
session.merge(certificateSigningRequest)
val certificateDataEntity = CertificateDataEntity(
certificateStatus = CertificateStatus.VALID,
certPath = certPath,
certificateSigningRequest = certificateSigningRequest,
certificateSerialNumber = certPath.x509Certificates.first().serialNumber)
session.persist(certificateDataEntity)
}
}
override fun saveRequest(request: PKCS10CertificationRequest): String {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val (legalName, exception) = try {
val legalName = parseLegalName(request)
// Return existing request ID, if request already exists for the same request.
validateRequest(legalName, request)?.let { return@transaction it }
Pair(legalName, null)
} catch (e: RequestValidationException) {
Pair(e.legalName, e)
}
val requestEntity = CertificateSigningRequestEntity(
requestId = SecureHash.randomSHA256().toString(),
legalName = legalName,
publicKeyHash = toSupportedPublicKey(request.subjectPublicKeyInfo).hash,
request = request,
remark = exception?.rejectMessage,
modifiedBy = CertificateSigningRequestStorage.DOORMAN_SIGNATURE,
status = if (exception == null) RequestStatus.NEW else RequestStatus.REJECTED
)
session.save(requestEntity) as String
}
}
private fun DatabaseTransaction.findRequest(requestId: String,
requestStatus: RequestStatus? = null): CertificateSigningRequestEntity? {
return uniqueEntityWhere { builder, path ->
val idClause = builder.equal(path.get<String>(CertificateSigningRequestEntity::requestId.name), requestId)
if (requestStatus == null) {
idClause
} else {
val statusClause = builder.equal(path.get<String>(CertificateSigningRequestEntity::status.name), requestStatus)
builder.and(idClause, statusClause)
}
}
}
override fun markRequestTicketCreated(requestId: String) {
database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val request = requireNotNull(findRequest(requestId, RequestStatus.NEW)) { "Error when creating request ticket with id: $requestId. Request does not exist or its status is not NEW." }
val update = request.copy(
modifiedAt = Instant.now(),
status = RequestStatus.TICKET_CREATED)
session.merge(update)
}
}
override fun approveRequest(requestId: String, approvedBy: String) {
database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
findRequest(requestId, RequestStatus.TICKET_CREATED)?.let {
val update = it.copy(
modifiedBy = approvedBy,
modifiedAt = Instant.now(),
status = RequestStatus.APPROVED)
session.merge(update)
}
}
}
override fun rejectRequest(requestId: String, rejectedBy: String, rejectReason: String?) {
database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val request = requireNotNull(findRequest(requestId)) { "Error when rejecting request with id: $requestId. Request does not exist." }
val update = request.copy(
modifiedBy = rejectedBy,
modifiedAt = Instant.now(),
status = RequestStatus.REJECTED,
remark = rejectReason
)
session.merge(update)
}
}
override fun getValidCertificatePath(publicKey: PublicKey): CertPath? {
return database.transaction {
session.createQuery(
"select a.certificateData.certPath from ${CertificateSigningRequestEntity::class.java.name} a " +
"where a.publicKeyHash = :publicKeyHash and a.status = 'DONE' and a.certificateData.certificateStatus = 'VALID'", CertPath::class.java)
.setParameter("publicKeyHash", publicKey.hash.toString())
.uniqueResult()
}
}
override fun getRequest(requestId: String): CertificateSigningRequest? {
return database.transaction {
findRequest(requestId)?.toCertificateSigningRequest()
}
}
override fun getRequests(requestStatus: RequestStatus): List<CertificateSigningRequest> {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val builder = session.criteriaBuilder
val query = builder.createQuery(CertificateSigningRequestEntity::class.java).run {
from(CertificateSigningRequestEntity::class.java).run {
where(builder.equal(get<RequestStatus>(CertificateSigningRequestEntity::status.name), requestStatus))
}
}
session.createQuery(query).resultList.map { it.toCertificateSigningRequest() }
}
}
private fun parseLegalName(request: PKCS10CertificationRequest): CordaX500Name {
return try {
CordaX500Name.build(X500Principal(request.subject.encoded))
} catch (e: IllegalArgumentException) {
throw RequestValidationException(null, request.subject.toString(), rejectMessage = "Name validation failed: ${e.message}")
}
}
/**
* Validate certificate signing request, returns request ID if same request already exists.
*/
private fun DatabaseTransaction.validateRequest(legalName: CordaX500Name, request: PKCS10CertificationRequest): String? {
// Check if the same request exists and returns the request id.
val existingRequestByPubKeyHash = nonRejectedRequest(CertificateSigningRequestEntity::publicKeyHash.name, toSupportedPublicKey(request.subjectPublicKeyInfo).hash)
existingRequestByPubKeyHash?.let {
// Compare subject, attribute.
// We cannot compare the request directly because it contains nonce.
if (certNotRevoked(it) && it.request.subject == request.subject && it.request.attributes.asList() == request.attributes.asList()) {
return it.requestId
} else {
//TODO Consider following scenario: There is a CSR that is signed but the certificate itself has expired or was revoked
throw RequestValidationException(legalName, rejectMessage = "Duplicate public key")
}
}
// Check if requested role is valid.
if (request.getCertRole() !in allowedCertRoles)
throw RequestValidationException(legalName, rejectMessage = "Requested certificate role ${request.getCertRole()} is not allowed.")
// TODO consider scenario: There is a CSR that is signed but the certificate itself has expired or was revoked
// Also, at the moment we assume that once the CSR is approved it cannot be rejected.
// What if we approved something by mistake.
val existingRequestByLegalName = nonRejectedRequest(CertificateSigningRequestEntity::legalName.name, legalName)
existingRequestByLegalName?.let {
if (certNotRevoked(it)) {
throw RequestValidationException(legalName, rejectMessage = "Duplicate legal name")
}
}
return null
}
private fun certNotRevoked(request: CertificateSigningRequestEntity): Boolean {
return request.status != RequestStatus.DONE || request.certificateData?.certificateStatus != CertificateStatus.REVOKED
}
/**
* Retrieve "non-rejected" request which matches provided column and value predicate.
*/
private fun <T : Any> DatabaseTransaction.nonRejectedRequest(columnName: String, value: T): CertificateSigningRequestEntity? {
val query = session.criteriaBuilder.run {
val criteriaQuery = createQuery(CertificateSigningRequestEntity::class.java)
criteriaQuery.from(CertificateSigningRequestEntity::class.java).run {
val valueQuery = equal(get<T>(columnName), value)
val statusQuery = notEqual(get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED)
criteriaQuery.where(and(valueQuery, statusQuery))
}
}
return session.createQuery(query).setLockMode(LockModeType.PESSIMISTIC_WRITE).setMaxResults(1).uniqueResult()
}
private data class RequestValidationException(val legalName: CordaX500Name?, val subjectName: String = legalName.toString(), val rejectMessage: String) : Exception("Validation failed for $subjectName. $rejectMessage.")
}

View File

@ -1,187 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.*
import com.r3.corda.networkmanage.common.utils.logger
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.network.NetworkMapAndSigned
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import java.time.Instant
/**
* Database implementation of the [NetworkMapStorage] interface
*/
class PersistentNetworkMapStorage(private val database: CordaPersistence) : NetworkMapStorage {
companion object {
// Used internally to identify global network map in database table.
private const val PUBLIC_NETWORK_ID = "PUBLIC_NETWORK"
}
override fun getNetworkMaps(): NetworkMaps {
return database.transaction {
val networkMapEntities = session.createQuery("from ${NetworkMapEntity::class.java.name}", NetworkMapEntity::class.java)
.resultList
.associateBy { it.id }
NetworkMaps(networkMapEntities[PUBLIC_NETWORK_ID], networkMapEntities.filterKeys { it != PUBLIC_NETWORK_ID })
}
}
override fun saveNewNetworkMap(networkId: String?, networkMapAndSigned: NetworkMapAndSigned) {
val (networkMap, signedNetworkMap) = networkMapAndSigned
database.transaction {
val networkParametersEntity = checkNotNull(getNetworkParametersEntity(networkMap.networkParameterHash)) {
"Network parameters ${networkMap.networkParameterHash} must be first persisted"
}
check(networkParametersEntity.isSigned) {
"Network parameters ${networkMap.networkParameterHash} are not signed"
}
session.merge(NetworkMapEntity(
id = networkId ?: PUBLIC_NETWORK_ID,
networkMap = networkMap,
signature = signedNetworkMap.sig.bytes,
certificate = signedNetworkMap.sig.by,
networkParameters = networkParametersEntity
))
}
}
override fun getSignedNetworkParameters(hash: SecureHash): SignedNetworkParameters? {
return database.transaction {
getNetworkParametersEntity(hash)?.let {
if (it.isSigned) it.toSignedNetworkParameters() else null
}
}
}
override fun getNodeInfoHashes(): NodeInfoHashes {
return database.transaction {
val currentParameters = getNetworkMaps().publicNetworkMap?.networkParameters?.networkParameters
val builder = session.criteriaBuilder
// TODO Convert this query to JPQL so it's more readable.
val query = builder.createTupleQuery().run {
from(NodeInfoEntity::class.java).run {
val certStatusExpression = get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name)
.get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name)
// TODO When revoking certs, all node-infos that point to it must be made non-current. Then this check
// isn't needed.
val certStatusEq = builder.equal(certStatusExpression, CertificateStatus.VALID)
val isCurrentNodeInfo = builder.isTrue(get<Boolean>(NodeInfoEntity::isCurrent.name))
// We enable eventHorizon only if minimum platform version is greater than 3, nodes on previous versions
// don't republish their node infos on regular intervals so they shouldn't be evicted from network after eventHorizon.
val eventHorizonAgo = if (currentParameters != null && currentParameters.minimumPlatformVersion >= 4) {
builder.greaterThanOrEqualTo(get<Instant>(NodeInfoEntity::publishedAt.name),
Instant.now().minus(currentParameters.eventHorizon))
} else {
builder.and() // This expression is always true. It's needed when eventHorizon isn't enabled.
}
val networkIdSelector = get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name)
.get<PrivateNetworkEntity>(CertificateSigningRequestEntity::privateNetwork.name)
.get<String>(PrivateNetworkEntity::networkId.name)
multiselect(networkIdSelector, get<String>(NodeInfoEntity::nodeInfoHash.name))
.where(builder.and(certStatusEq, isCurrentNodeInfo, eventHorizonAgo))
}
}
val allNodeInfos = session.createQuery(query).resultList.groupBy { it[0]?.toString() ?: PUBLIC_NETWORK_ID }.mapValues { it.value.map { SecureHash.parse(it.get(1, String::class.java)) } }
NodeInfoHashes(allNodeInfos[PUBLIC_NETWORK_ID] ?: emptyList(), allNodeInfos.filterKeys { it != PUBLIC_NETWORK_ID })
}
}
override fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): NetworkParametersEntity {
val serialized = networkParameters.serialize()
signature?.verify(serialized)
val hash = serialized.hash
return database.transaction {
val entity = getNetworkParametersEntity(hash)
val newNetworkParamsEntity = entity?.copy(
signature = signature?.bytes,
certificate = signature?.by
) ?: NetworkParametersEntity(
networkParameters = networkParameters,
hash = hash.toString(),
signature = signature?.bytes,
certificate = signature?.by
)
session.merge(newNetworkParamsEntity) as NetworkParametersEntity
}
}
override fun getLatestNetworkParameters(): NetworkParametersEntity? {
return database.transaction {
val query = session.criteriaBuilder.run {
createQuery(NetworkParametersEntity::class.java).run {
from(NetworkParametersEntity::class.java).run {
orderBy(desc(get<String>(NetworkParametersEntity::created.name)))
}
}
}
// We just want the last entry
session.createQuery(query).setMaxResults(1).uniqueResult()
}
}
override fun saveNewParametersUpdate(networkParameters: NetworkParameters, description: String, updateDeadline: Instant) {
database.transaction {
val existingUpdate = getCurrentParametersUpdate()
if (existingUpdate != null) {
logger.info("Cancelling existing update: $existingUpdate")
session.merge(existingUpdate.copy(status = UpdateStatus.CANCELLED))
}
val netParamsEntity = saveNetworkParameters(networkParameters, null)
session.save(ParametersUpdateEntity(
networkParameters = netParamsEntity,
description = description,
updateDeadline = updateDeadline
))
}
}
override fun getCurrentParametersUpdate(): ParametersUpdateEntity? {
return database.transaction {
val newParamsUpdates = session.fromQuery<ParametersUpdateEntity>("u where u.status in :statuses")
.setParameterList("statuses", listOf(UpdateStatus.NEW, UpdateStatus.FLAG_DAY))
.resultList
when (newParamsUpdates.size) {
0 -> null
1 -> newParamsUpdates[0]
else -> throw IllegalStateException("More than one update found: $newParamsUpdates")
}
}
}
override fun setParametersUpdateStatus(update: ParametersUpdateEntity, newStatus: UpdateStatus) {
require(newStatus != UpdateStatus.NEW)
database.transaction {
session.merge(update.copy(status = newStatus))
}
}
override fun switchFlagDay(update: ParametersUpdateEntity) {
database.transaction {
setParametersUpdateStatus(update, UpdateStatus.APPLIED)
session.createQuery("update ${NodeInfoEntity::class.java.name} n set n.isCurrent = false " +
"where (n.acceptedParametersUpdate != :acceptedParamUp or n.acceptedParametersUpdate is null) and n.isCurrent = true")
.setParameter("acceptedParamUp", update).executeUpdate()
}
}
}
internal fun DatabaseTransaction.getNetworkParametersEntity(hash: SecureHash): NetworkParametersEntity? {
return uniqueEntityWhere { builder, path ->
builder.equal(path.get<String>(NetworkParametersEntity::hash.name), hash.toString())
}
}

View File

@ -1,134 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRequestEntity
import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity
import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity
import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus
import com.r3.corda.networkmanage.common.utils.logger
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.CertRole
import net.corda.core.internal.CertRole.NODE_CA
import net.corda.core.internal.hash
import net.corda.core.utilities.debug
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import java.security.PublicKey
import java.security.cert.CertPath
import java.time.Instant
/**
* Database implementation of the [NetworkMapStorage] interface
*/
class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage {
override fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash {
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
val nodeInfoHash = signedNodeInfo.raw.hash
// Extract identities issued by the intermediate CAs (doorman).
val registeredIdentities = nodeInfo.legalIdentitiesAndCerts.map { it.certPath.x509Certificates.single { CertRole.extract(it) in setOf(CertRole.SERVICE_IDENTITY, NODE_CA) } }
database.transaction {
// Record fact of republishing of the node info, it's treated as a heartbeat from the node.
val rowsUpdated = session.createQuery("update ${NodeInfoEntity::class.java.name} n set publishedAt = :now " +
"where n.nodeInfoHash = :nodeInfoHash and n.isCurrent = true")
.setParameter("now", Instant.now())
.setParameter("nodeInfoHash", nodeInfoHash.toString())
.executeUpdate()
if (rowsUpdated != 0) {
logger.debug { "Republish of $nodeInfo" }
return@transaction nodeInfoHash
}
// TODO Move these checks out of data access layer
// For each identity known by the doorman, validate against it's CSR.
val requests = registeredIdentities.map {
val request = requireNotNull(getSignedRequestByPublicHash(it.publicKey.hash)) {
"Node-info not registered with us"
}
request.certificateData?.certificateStatus.let {
require(it == CertificateStatus.VALID) { "Certificate is no longer valid: $it" }
}
CertRole.extract(it) to request
}
// Ensure only 1 NodeCA identity.
// TODO: make this support multiple node identities.
val (_, request) = requireNotNull(requests.singleOrNull { it.first == CertRole.NODE_CA }) { "Require exactly 1 Node CA identity in the node-info." }
val existingNodeInfos = session.fromQuery<NodeInfoEntity>(
"n where n.certificateSigningRequest = :csr and n.isCurrent = true order by n.publishedAt desc")
.setParameter("csr", request)
.resultList
// Update any [NodeInfoEntity] instance for this CSR as not current.
existingNodeInfos.forEach { session.merge(it.copy(isCurrent = false)) }
session.saveOrUpdate(NodeInfoEntity(
nodeInfoHash = nodeInfoHash.toString(),
publicKeyHash = nodeInfo.legalIdentities[0].owningKey.hash,
certificateSigningRequest = request,
signedNodeInfo = signedNodeInfo,
isCurrent = true,
acceptedParametersUpdate = existingNodeInfos.firstOrNull()?.acceptedParametersUpdate
))
}
return nodeInfoHash
}
override fun getNodeInfo(nodeInfoHash: SecureHash): SignedNodeInfo? {
return database.transaction {
session.find(NodeInfoEntity::class.java, nodeInfoHash.toString())?.signedNodeInfo
}
}
override fun getAcceptedParametersUpdate(nodeInfoHash: SecureHash): ParametersUpdateEntity? {
return database.transaction {
session.find(NodeInfoEntity::class.java, nodeInfoHash.toString())?.acceptedParametersUpdate
}
}
override fun getCertificatePath(publicKeyHash: SecureHash): CertPath? {
return database.transaction {
val request = getSignedRequestByPublicHash(publicKeyHash)
request?.let { it.certificateData!!.certPath }
}
}
override fun ackNodeInfoParametersUpdate(publicKey: PublicKey, acceptedParametersHash: SecureHash) {
return database.transaction {
val nodeInfoEntity = session.fromQuery<NodeInfoEntity>(
"n where n.publicKeyHash = :publicKeyHash and isCurrent = true")
.setParameter("publicKeyHash", publicKey.hash)
.singleResult
val parametersUpdateEntity = session.fromQuery<ParametersUpdateEntity>(
"u where u.networkParameters.hash = :acceptedParametersHash").
setParameter("acceptedParametersHash", acceptedParametersHash.toString())
.singleResult
require(parametersUpdateEntity.status in listOf(UpdateStatus.NEW, UpdateStatus.FLAG_DAY)) {
"$parametersUpdateEntity can no longer be accepted as it's ${parametersUpdateEntity.status}"
}
session.merge(nodeInfoEntity.copy(acceptedParametersUpdate = parametersUpdateEntity))
}
}
private fun DatabaseTransaction.getSignedRequestByPublicHash(publicKeyHash: SecureHash): CertificateSigningRequestEntity? {
return uniqueEntityWhere { builder, path ->
val publicKeyEq = builder.equal(path.get<String>(CertificateSigningRequestEntity::publicKeyHash.name), publicKeyHash)
val statusEq = builder.equal(path.get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.DONE)
builder.and(publicKeyEq, statusEq)
}
}
}

View File

@ -1,67 +0,0 @@
package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.utils.buildCertPath
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.serialize
import net.corda.core.utilities.sequence
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.network.NetworkMap
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.cert.CertPath
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import javax.persistence.AttributeConverter
class PKCS10CertificationRequestConverter : AttributeConverter<PKCS10CertificationRequest, ByteArray> {
override fun convertToEntityAttribute(dbData: ByteArray?): PKCS10CertificationRequest? = dbData?.let(::PKCS10CertificationRequest)
override fun convertToDatabaseColumn(attribute: PKCS10CertificationRequest?): ByteArray? = attribute?.encoded
}
class CertPathConverter : AttributeConverter<CertPath, ByteArray> {
override fun convertToEntityAttribute(dbData: ByteArray?): CertPath? = dbData?.let(::buildCertPath)
override fun convertToDatabaseColumn(attribute: CertPath?): ByteArray? = attribute?.encoded
}
class X509CertificateConverter : AttributeConverter<X509Certificate, ByteArray> {
override fun convertToEntityAttribute(dbData: ByteArray?): X509Certificate? {
return dbData?.let { X509CertificateFactory().generateCertificate(it.inputStream()) }
}
override fun convertToDatabaseColumn(attribute: X509Certificate?): ByteArray? = attribute?.encoded
}
class X509CRLConverter : AttributeConverter<X509CRL, ByteArray> {
override fun convertToEntityAttribute(dbData: ByteArray?): X509CRL? {
return dbData?.let { X509CertificateFactory().delegate.generateCRL(it.inputStream()) as X509CRL }
}
override fun convertToDatabaseColumn(attribute: X509CRL?): ByteArray? = attribute?.encoded
}
class NetworkParametersConverter : CordaSerializationConverter<NetworkParameters>(NetworkParameters::class.java)
class NetworkMapConverter : CordaSerializationConverter<NetworkMap>(NetworkMap::class.java)
class SignedNodeInfoConverter : CordaSerializationConverter<SignedNodeInfo>(SignedNodeInfo::class.java)
class CordaX500NameAttributeConverter : ToStringConverter<CordaX500Name>(CordaX500Name.Companion::parse)
class SecureHashAttributeConverter : ToStringConverter<SecureHash>(SecureHash.Companion::parse)
abstract class CordaSerializationConverter<T : Any>(private val clazz: Class<T>) : AttributeConverter<T, ByteArray> {
override fun convertToEntityAttribute(dbData: ByteArray?): T? {
return dbData?.let {
val serializationFactory = SerializationFactory.defaultFactory
serializationFactory.deserialize(it.sequence(), clazz, serializationFactory.defaultContext)
}
}
override fun convertToDatabaseColumn(attribute: T?): ByteArray? = attribute?.serialize()?.bytes
}
abstract class ToStringConverter<T : Any>(private val parser: (String) -> T) : AttributeConverter<T, String> {
override fun convertToDatabaseColumn(attribute: T?): String? = attribute?.toString()
override fun convertToEntityAttribute(dbData: String?): T? = dbData?.let { parser(it) }
}

View File

@ -1,30 +0,0 @@
package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import java.io.Serializable
import java.security.cert.X509CRL
import java.time.Instant
import javax.persistence.*
@Entity
@Table(name = "cert_revocation_list")
class CertificateRevocationListEntity(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Long? = null,
@Column(name = "issuer", length = 16, nullable = false, columnDefinition = "NVARCHAR(16)")
@Enumerated(EnumType.STRING)
val crlIssuer: CrlIssuer,
@Lob
@Column(name = "crl_bytes", nullable = false)
@Convert(converter = X509CRLConverter::class)
val crl: X509CRL,
@Column(name = "signed_by", length = 64, nullable = false)
val signedBy: String,
@Column(name = "modified_at", nullable = false)
val modifiedAt: Instant = Instant.now()
) : Serializable

View File

@ -1,61 +0,0 @@
package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.core.identity.CordaX500Name
import org.hibernate.envers.Audited
import java.io.Serializable
import java.math.BigInteger
import java.security.cert.CRLReason
import java.time.Instant
import javax.persistence.*
@Entity
@Table(name = "cert_revocation_request")
data class CertificateRevocationRequestEntity(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Long? = null,
@Column(name = "request_id", length = 64, nullable = false, unique = true)
val requestId: String,
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "cert_data", nullable = false)
val certificateData: CertificateDataEntity,
@Column(name = "cert_serial_number", nullable = false, columnDefinition = "NUMERIC(28)")
val certificateSerialNumber: BigInteger,
@Column(name = "legal_name", length = 256, nullable = false)
@Convert(converter = CordaX500NameAttributeConverter::class)
val legalName: CordaX500Name,
// Setting [columnDefinition] is a work around for a hibernate problem when using SQL database.
// TODO: Remove this when we find out the cause of the problem.
@Audited
@Column(name = "status", length = 16, nullable = false, columnDefinition = "NVARCHAR(16)")
@Enumerated(EnumType.STRING)
val status: RequestStatus = RequestStatus.NEW,
@Column(name = "reporter", nullable = false, length = 64)
val reporter: String,
@Audited
@Column(name = "modified_by", nullable = false, length = 64)
val modifiedBy: String,
@Audited
@Column(name = "modified_at", nullable = false)
val modifiedAt: Instant = Instant.now(),
// Setting [columnDefinition] is a work around for a hibernate problem when using SQL database.
// TODO: Remove this when we find out the cause of the problem.
@Audited
@Column(name = "revocation_reason", length = 32, nullable = false, columnDefinition = "NVARCHAR(32)")
@Enumerated(EnumType.STRING)
val revocationReason: CRLReason,
@Audited
@Column(name = "remark", length = 256)
val remark: String? = null
) : Serializable

View File

@ -1,130 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.persistence.CertificateData
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest
import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.x509Certificates
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.hibernate.envers.Audited
import java.io.Serializable
import java.math.BigInteger
import java.security.cert.CertPath
import java.time.Instant
import javax.persistence.*
@Entity
@Table(name = "cert_signing_request", indexes = arrayOf(Index(name = "IDX__CSR__PKH", columnList = "public_key_hash")))
data class CertificateSigningRequestEntity(
@Id
@Column(name = "request_id", length = 64, nullable = false)
val requestId: String,
// TODO: Store X500Name with a proper schema.
@Column(name = "legal_name", length = 256)
@Convert(converter = CordaX500NameAttributeConverter::class)
val legalName: CordaX500Name?,
@Column(name = "public_key_hash", length = 64, nullable = false)
@Convert(converter = SecureHashAttributeConverter::class)
val publicKeyHash: SecureHash,
// Setting [columnDefinition] is a work around for a hibernate problem when using SQL database.
// TODO: Remove this when we find out the cause of the problem.
@Audited
@Column(name = "status", length = 16, nullable = false, columnDefinition = "NVARCHAR(16)")
@Enumerated(EnumType.STRING)
val status: RequestStatus = RequestStatus.NEW,
@Audited
@Column(name = "modified_by", length = 64, nullable = false)
val modifiedBy: String,
// TODO: Use audit framework instead.
@Column(name = "modified_at", nullable = false)
val modifiedAt: Instant = Instant.now(),
@Audited
@Column(name = "remark", length = 256)
val remark: String? = null,
@OneToOne(fetch = FetchType.EAGER, mappedBy = "certificateSigningRequest")
val certificateData: CertificateDataEntity? = null,
@Lob
@Column(name = "request_bytes", nullable = false)
@Convert(converter = PKCS10CertificationRequestConverter::class)
val request: PKCS10CertificationRequest,
@ManyToOne
@JoinColumn(name = "private_network", foreignKey = ForeignKey(name = "FK__CSR__PN"))
val privateNetwork: PrivateNetworkEntity? = null
) : Serializable {
fun toCertificateSigningRequest(): CertificateSigningRequest {
return CertificateSigningRequest(
requestId = requestId,
legalName = legalName,
publicKeyHash = publicKeyHash,
status = status,
request = request,
remark = remark,
modifiedBy = modifiedBy,
certData = certificateData?.toCertificateData()
)
}
}
@Entity
@Table(name = "cert_data")
data class CertificateDataEntity(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Long? = null,
// Setting [columnDefinition] is a work around for a hibernate problem when using SQL database.
// TODO: Remove this when we find out the cause of the problem.
@Column(name = "cert_status", length = 16, nullable = false, columnDefinition = "NVARCHAR(16)")
@Enumerated(EnumType.STRING)
val certificateStatus: CertificateStatus,
@Lob
@Column(name = "cert_path_bytes", nullable = false)
@Convert(converter = CertPathConverter::class)
val certPath: CertPath,
@OneToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "cert_signing_request", foreignKey = ForeignKey(name = "FK__CD__CSR"), nullable = false)
val certificateSigningRequest: CertificateSigningRequestEntity,
@Column(name = "cert_serial_number", unique = true, nullable = false, columnDefinition = "NUMERIC(28)")
val certificateSerialNumber: BigInteger
) : Serializable {
fun toCertificateData(): CertificateData = CertificateData(certificateStatus, certPath)
val legalName: CordaX500Name get() {
return CordaX500Name.build(certPath.x509Certificates[0].subjectX500Principal)
}
}
@Entity
@Table(name = "private_network")
data class PrivateNetworkEntity(
@Id
@Column(name = "id", length = 64)
val networkId: String,
@Column(name = "name", length = 255, nullable = false)
val networkName: String
) : Serializable

View File

@ -1,57 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence.entity
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.SignedNetworkMap
import org.hibernate.envers.Audited
import org.hibernate.envers.RelationTargetAuditMode
import java.io.Serializable
import java.security.cert.X509Certificate
import java.time.Instant
import javax.persistence.*
@Entity
@Audited
@Table(name = "network_map")
class NetworkMapEntity(
@Id
@Column(name = "id")
val id: String,
@Lob
@Column(name = "serialized_network_map", nullable = false)
@Convert(converter = NetworkMapConverter::class)
val networkMap: NetworkMap,
@Lob
@Column(name = "signature", nullable = false)
val signature: ByteArray,
@Lob
@Column(name = "cert", nullable = false)
@Convert(converter = X509CertificateConverter::class)
val certificate: X509Certificate,
@ManyToOne(optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "network_parameters", nullable = false)
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
val networkParameters: NetworkParametersEntity,
@Column(nullable = false)
val timestamp: Instant = Instant.now()
) : Serializable {
fun toSignedNetworkMap(): SignedNetworkMap {
return SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(certificate, signature))
}
}

View File

@ -1,71 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence.entity
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import java.io.Serializable
import java.security.cert.X509Certificate
import java.time.Instant
import javax.persistence.*
@Entity
@Table(name = "network_parameters", indexes = arrayOf(Index(name = "IDX_NP_HASH", columnList = "hash")))
class NetworkParametersEntity(
@Id
@Column(name = "hash", length = 64, nullable = false)
// AttributeConverters can't be used on @Id attributes, otherwise this would be SecureHash
val hash: String,
@Column(nullable = false)
val created: Instant = Instant.now(),
@Lob
@Column(name = "parameters_bytes", nullable = false)
@Convert(converter = NetworkParametersConverter::class)
val networkParameters: NetworkParameters,
// Both of the fields below are nullable, because of the way we sign network map data. NetworkParameters can be
// inserted into database without signature. Then signing service will sign them.
@Lob
@Column(name = "signature")
val signature: ByteArray?,
@Lob
@Column(name = "cert")
@Convert(converter = X509CertificateConverter::class)
val certificate: X509Certificate?
) : Serializable {
val isSigned: Boolean get() = certificate != null && signature != null
fun toSignedNetworkParameters(): SignedNetworkParameters {
val cert = certificate ?: throw IllegalStateException("Network parameters entity is not signed: $hash")
val sign = signature ?: throw IllegalStateException("Network parameters entity is not signed: $hash")
return SignedNetworkParameters(networkParameters.serialize(), DigitalSignatureWithCert(cert, sign))
}
fun copy(parametersHash: String = this.hash,
created: Instant = this.created,
networkParameters: NetworkParameters = this.networkParameters,
signature: ByteArray? = this.signature,
certificate: X509Certificate? = this.certificate
): NetworkParametersEntity {
return NetworkParametersEntity(
hash = parametersHash,
created = created,
networkParameters = networkParameters,
signature = signature,
certificate = certificate
)
}
}

View File

@ -1,52 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.persistence.entity
import net.corda.core.crypto.SecureHash
import net.corda.nodeapi.internal.SignedNodeInfo
import org.hibernate.annotations.UpdateTimestamp
import java.io.Serializable
import java.time.Instant
import javax.persistence.*
@Entity
@Table(name = "node_info")
data class NodeInfoEntity(
// Hash of serialized [NodeInfo] without signatures.
@Id
@Column(name = "node_info_hash", length = 64)
// AttributeConverters can't be used on @Id attributes, otherwise this would be SecureHash
val nodeInfoHash: String,
@Column(name = "public_key_hash", length = 64)
@Convert(converter = SecureHashAttributeConverter::class)
val publicKeyHash: SecureHash,
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "cert_signing_request", nullable = false)
val certificateSigningRequest: CertificateSigningRequestEntity,
@Lob
@Column(name = "signed_node_info_bytes", nullable = false)
@Convert(converter = SignedNodeInfoConverter::class)
val signedNodeInfo: SignedNodeInfo,
@Column(name = "is_current", nullable = false)
val isCurrent: Boolean,
@Column(name = "published_at", nullable = false)
@UpdateTimestamp
val publishedAt: Instant = Instant.now(),
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "accepted_params_update")
val acceptedParametersUpdate: ParametersUpdateEntity?
) : Serializable

View File

@ -1,49 +0,0 @@
package com.r3.corda.networkmanage.common.persistence.entity
import net.corda.core.crypto.SecureHash
import net.corda.nodeapi.internal.network.ParametersUpdate
import java.io.Serializable
import java.time.Instant
import javax.persistence.*
@Entity
@Table(name = "parameters_update")
data class ParametersUpdateEntity(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Long? = null,
// TODO use @MapsId to get rid of additional sequence
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "network_parameters", foreignKey = ForeignKey(name = "FK__param_up__net_param"))
val networkParameters: NetworkParametersEntity,
@Column(name = "description", nullable = false)
val description: String,
@Column(name = "update_deadline", nullable = false)
val updateDeadline: Instant,
@Column(name = "status", length = 16, nullable = false, columnDefinition = "NVARCHAR(16)")
@Enumerated(EnumType.STRING)
val status: UpdateStatus = UpdateStatus.NEW
) : Serializable {
fun toParametersUpdate(): ParametersUpdate {
return ParametersUpdate(SecureHash.parse(networkParameters.hash), description, updateDeadline)
}
}
enum class UpdateStatus {
/** A newly created update. */
NEW,
/**
* An update that has passed its deadline and flagged to be made active on the next signing event. At most only one
* update with status either NEW or FLAG_DAY can exist.
*/
FLAG_DAY,
/** Any previously flagged update that has been activated into the network map. */
APPLIED,
/** A new or flag day update that has been cancelled. */
CANCELLED
}

View File

@ -1,62 +0,0 @@
package com.r3.corda.networkmanage.common.signer
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.utils.Revocation
import com.r3.corda.networkmanage.common.utils.createSignedCrl
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.trace
import java.net.URL
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.Instant
import java.util.*
class CertificateRevocationListSigner(
private val revocationListStorage: CertificateRevocationListStorage,
private val issuerCertificate: X509Certificate,
private val updateInterval: Duration,
private val endpoint: URL,
private val signer: Signer) {
private companion object {
private val logger = contextLogger()
}
/**
* Builds, signs and persists a new certificate revocation list.
* It happens only if there are new entries to be added to the current list.
*
* @param newCRRs list of approved certificate revocation requests. An approved certificate revocation request
* is a request that has status [RequestStatus.APPROVED] - i.e. it has not yet been included in any CRL.
* @param existingCRRs list of revoked certificate revocation requests. A revoked certificate revocation request
* is a request that has status [RequestStatus.DONE] - i.e. it has already been included in another CRL.
* @param signedBy who signs this CRL.
*
* @return A new signed CRL containing both revoked and approved certificate revocation requests.
*/
fun createSignedCRL(newCRRs: List<CertificateRevocationRequestData>,
existingCRRs: List<CertificateRevocationRequestData>,
signedBy: String): X509CRL {
require(existingCRRs.all { it.status == RequestStatus.DONE }) { "All existing CRRs need to be in the ${RequestStatus.DONE} state" }
require(newCRRs.all { it.status == RequestStatus.APPROVED }) { "All newly included CRRs need to be in the ${RequestStatus.APPROVED} state" }
logger.info("Signing a new Certificate Revocation List...")
logger.debug("Retrieving approved Certificate Revocation Requests...")
val revocationTime = Instant.now()
val approvedWithTimestamp = newCRRs.map { it.copy(modifiedAt = revocationTime) }
logger.trace { "Approved Certificate Revocation Requests to be included in the new Certificate Revocation List: $approvedWithTimestamp" }
logger.debug("Retrieving revoked Certificate Revocation Requests...")
logger.trace { "Revoked Certificate Revocation Requests to be included in the new Certificate Revocation List: $existingCRRs" }
val revocations = (existingCRRs + approvedWithTimestamp).map {
Revocation(it.certificateSerialNumber, Date(it.modifiedAt.toEpochMilli()), it.reason)
}
val crl = createSignedCrl(issuerCertificate, endpoint, updateInterval, signer, revocations)
logger.debug { "Created a new Certificate Revocation List $crl" }
revocationListStorage.saveCertificateRevocationList(crl, CrlIssuer.DOORMAN, signedBy, revocationTime)
logger.info("A new Certificate Revocation List has been persisted.")
return crl
}
}

View File

@ -1,104 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.signer
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity
import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus.FLAG_DAY
import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus.NEW
import com.r3.corda.networkmanage.common.utils.join
import net.corda.core.crypto.SecureHash
import net.corda.core.node.NetworkParameters
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkMapAndSigned
import net.corda.nodeapi.internal.network.ParametersUpdate
class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val signer: Signer) {
private companion object {
val logger = contextLogger()
}
/**
* Signs the network map and latest network parameters if they haven't been signed yet.
*/
fun signNetworkMaps() {
val (publicNetworkMap, privateNetworkMaps) = networkMapStorage.getNetworkMaps()
val (publicNodeInfoHashes, privateNodeInfoHashes) = networkMapStorage.getNodeInfoHashes()
val (networkParameterHash, parametersUpdate) = maybeUpdateNetworkParameters(publicNetworkMap?.networkMap?.networkParameterHash)
logger.debug { "Current network parameters: $networkParameterHash" }
// Process public network map.
maybeSignNetworkMap(publicNetworkMap, publicNodeInfoHashes, parametersUpdate, networkParameterHash)
// Process each private network map.
privateNetworkMaps.join(privateNodeInfoHashes).forEach { networkId, (currentNetworkMap, nodeInfoHashes) ->
maybeSignNetworkMap(currentNetworkMap, nodeInfoHashes, parametersUpdate, networkParameterHash, networkId)
}
}
private fun maybeSignNetworkMap(currentNetworkMap: NetworkMapEntity?, nodeInfoHashes: List<SecureHash>?, parametersUpdate: ParametersUpdate?, networkParameterHash: SecureHash, networkId: String? = null) {
val printableNetworkId = networkId ?: "Public Network"
if (currentNetworkMap == null) {
logger.info("There is currently no network map for network '$printableNetworkId'")
} else {
logger.debug { "Current network map for network '$printableNetworkId': ${currentNetworkMap.networkMap}" }
}
logger.debug { "Retrieved node info hashes for network '$printableNetworkId' :\n${nodeInfoHashes?.joinToString("\n")}" }
val newNetworkMap = NetworkMap(nodeInfoHashes ?: emptyList(), networkParameterHash, parametersUpdate)
logger.debug { "Potential new network map for network '$printableNetworkId': $newNetworkMap" }
if (currentNetworkMap?.networkMap != newNetworkMap) {
val newNetworkMapAndSigned = NetworkMapAndSigned(newNetworkMap) { signer.signBytes(it.bytes) }
networkMapStorage.saveNewNetworkMap(networkId, newNetworkMapAndSigned)
logger.info("Signed new network map for network '$printableNetworkId' : $newNetworkMap")
} else {
logger.debug("Current network map for network '$printableNetworkId' is up-to-date")
}
}
private fun maybeUpdateNetworkParameters(currentNetworkParametersHash: SecureHash?): Pair<SecureHash, ParametersUpdate?> {
val latestNetworkParameters = requireNotNull(networkMapStorage.getLatestNetworkParameters()) { "No network parameters present" }
logger.debug { "Retrieved latest network parameters: ${latestNetworkParameters.networkParameters}" }
val parametersUpdate = networkMapStorage.getCurrentParametersUpdate()
logger.debug { "Retrieved parameters update: $parametersUpdate" }
check(parametersUpdate == null || parametersUpdate.networkParameters.hash == latestNetworkParameters.hash) {
"The latest network parameters are not the scheduled updated ones"
}
// We persist signed parameters only if they were not persisted before (they are not in currentSignedNetworkMap as
// normal parameters or as an update)
if (!latestNetworkParameters.isSigned) {
signAndPersistNetworkParameters(latestNetworkParameters.networkParameters)
} else {
logger.debug { "No need to sign any network parameters as they're up-to-date" }
}
val parametersToNetworkMap = if (parametersUpdate?.status == FLAG_DAY || currentNetworkParametersHash == null) {
parametersUpdate?.let { networkMapStorage.switchFlagDay(it) }
SecureHash.parse(latestNetworkParameters.hash)
} else {
currentNetworkParametersHash
}
return Pair(parametersToNetworkMap, parametersUpdate?.let { if (it.status == NEW) it.toParametersUpdate() else null })
}
fun signAndPersistNetworkParameters(networkParameters: NetworkParameters) {
networkMapStorage.saveNetworkParameters(networkParameters, signer.signObject(networkParameters).sig)
logger.info("Signed network parameters: $networkParameters")
}
}

View File

@ -1,34 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.signer
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.serialization.serialize
/**
* An interface for arbitrary data signing functionality.
*/
interface Signer {
/**
* Signs given bytes. The signing key selection strategy is left to the implementing class.
* @return [DigitalSignatureWithCert] that encapsulates the signature and the certificate path used in the signing process.
* @throws [AuthenticationException] if fails authentication
*/
fun signBytes(data: ByteArray): DigitalSignatureWithCert
fun <T : Any> signObject(obj: T): SignedDataWithCert<T> {
val serialised = obj.serialize()
return SignedDataWithCert(serialised, signBytes(serialised.bytes))
}
}
class AuthenticationException : Exception()

View File

@ -1,10 +0,0 @@
package com.r3.corda.networkmanage.common.sockets
import net.corda.core.serialization.CordaSerializable
import java.security.cert.X509CRL
import java.time.Instant
@CordaSerializable
data class CertificateRevocationListSubmission(val list: X509CRL,
val signer: String,
val revocationTime: Instant)

View File

@ -1,28 +0,0 @@
package com.r3.corda.networkmanage.common.sockets
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.core.serialization.CordaSerializable
import java.security.cert.X509CRL
@CordaSerializable
interface CrrSocketMessage
/**
* CRL retrieval message type
*/
class CrlRetrievalMessage : CrrSocketMessage
/**
* CRL response message type
*/
data class CrlResponseMessage(val crl: X509CRL?) : CrrSocketMessage
/**
* CRL submission message type
*/
class CrlSubmissionMessage : CrrSocketMessage
/**
* By status CRRs retrieval message type
*/
data class CrrsByStatusMessage(val status: RequestStatus) : CrrSocketMessage

View File

@ -1,29 +0,0 @@
package com.r3.corda.networkmanage.common.utils
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.amqpMagic
import java.util.concurrent.ConcurrentHashMap
class AMQPNetworkServicesSerializationScheme (
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor() : this(emptySet(), ConcurrentHashMap())
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException()
}
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException()
}
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) =
(magic == amqpMagic && target == SerializationContext.UseCase.P2P)
}

View File

@ -1,52 +0,0 @@
package com.r3.corda.networkmanage.common.utils
import joptsimple.OptionException
import joptsimple.OptionParser
import joptsimple.OptionSet
import java.io.PrintStream
import kotlin.system.exitProcess
// TODO: This class could be useful for the rest of the codebase.
abstract class ArgsParser<out T : Any> {
protected val optionParser = OptionParser()
private val helpOption = optionParser.acceptsAll(listOf("h", "help"), "show help").forHelp()
/**
If [printHelpOn] output stream is not null, this method will print help message and exit process
when encountered any error during args parsing, or when help flag is set.
*/
fun parseOrExit(vararg args: String, printHelpOn: PrintStream? = System.out): T {
val optionSet = try {
optionParser.parse(*args)
} catch (e: OptionException) {
printHelpOn?.let {
it.println(e.message ?: "Unable to parse arguments.")
optionParser.printHelpOn(it)
exitProcess(1)
}
throw e
}
if (optionSet.has(helpOption)) {
printHelpOn?.let(optionParser::printHelpOn)
exitProcess(0)
}
return try {
parse(optionSet)
} catch (e: RuntimeException) {
// We handle errors from the parsing of the command line arguments as a runtime
// exception because the joptsimple library is overly helpful and doesn't expose
// parsing / conversion exceptions in a way that makes reporting the message out
// to the user possible. Thus, that library is re-throwing those exceptions as simple
// runtime exceptions with a modified cause to preserve the error location
printHelpOn?.let {
it.println("ERROR: ${e.message ?: "Unable to parse arguments."}")
exitProcess(2)
}
throw e
}
}
protected abstract fun parse(optionSet: OptionSet): T
}

View File

@ -1,62 +0,0 @@
package com.r3.corda.networkmanage.common.utils
import com.r3.corda.networkmanage.common.signer.Signer
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.cert.X509v2CRLBuilder
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.ContentSigner
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.math.BigInteger
import java.net.URL
import java.security.cert.CRLReason
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.Instant
import java.util.*
fun createSignedCrl(issuerCertificate: X509Certificate,
endpointUrl: URL,
nextUpdateInterval: Duration,
signer: Signer,
includeInCrl: List<Revocation>,
indirectIssuingPoint: Boolean = false): X509CRL {
val extensionUtils = JcaX509ExtensionUtils()
val builder = X509v2CRLBuilder(X500Name.getInstance(issuerCertificate.subjectX500Principal.encoded), Date())
builder.addExtension(Extension.authorityKeyIdentifier, false, extensionUtils.createAuthorityKeyIdentifier(issuerCertificate))
val issuingDistributionPointName = GeneralName(GeneralName.uniformResourceIdentifier, endpointUrl.toString())
val issuingDistributionPoint = IssuingDistributionPoint(DistributionPointName(GeneralNames(issuingDistributionPointName)), indirectIssuingPoint, false)
builder.addExtension(Extension.issuingDistributionPoint, true, issuingDistributionPoint)
builder.setNextUpdate(Date(Instant.now().toEpochMilli() + nextUpdateInterval.toMillis()))
includeInCrl.forEach {
builder.addCRLEntry(it.certificateSerialNumber, it.date, it.reason.ordinal)
}
val crlHolder = builder.build(CrlContentSigner(signer))
return JcaX509CRLConverter().setProvider(BouncyCastleProvider()).getCRL(crlHolder)
}
data class Revocation(val certificateSerialNumber: BigInteger, val date: Date, val reason: CRLReason)
private class CrlContentSigner(private val signer: Signer) : ContentSigner {
private val outputStream = ByteArrayOutputStream()
override fun getAlgorithmIdentifier(): AlgorithmIdentifier = X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME.signatureOID
override fun getOutputStream(): OutputStream = outputStream
override fun getSignature(): ByteArray = signer.signBytes(outputStream.toByteArray()).bytes
}
enum class SupportedCrlReasons {
UNSPECIFIED,
KEY_COMPROMISE,
CA_COMPROMISE,
AFFILIATION_CHANGED,
SUPERSEDED,
CESSATION_OF_OPERATION,
PRIVILEGE_WITHDRAWN
}

View File

@ -1,26 +0,0 @@
package com.r3.corda.networkmanage.common.utils
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.InputStream
import java.io.OutputStream
inline fun <reified T : Any> InputStream.readObject(): T {
DataInputStream(this).run {
val messageSize = this.readInt()
val messageBytes = ByteArray(messageSize)
this.read(messageBytes)
return messageBytes.deserialize()
}
}
fun OutputStream.writeObject(message: Any) {
DataOutputStream(this).run {
val messageBytes = message.serialize().bytes
this.writeInt(messageBytes.size)
this.write(messageBytes)
this.flush()
}
}

View File

@ -1,86 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.common.utils
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRenderOptions
import net.corda.core.CordaOID
import net.corda.core.internal.CertRole
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
import net.corda.serialization.internal.SerializationFactoryImpl
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.x500.style.BCStyle
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.nio.file.Path
import java.security.KeyPair
import java.security.PrivateKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
const val CORDA_NETWORK_MAP = "cordanetworkmap"
const val NETWORK_ROOT_TRUSTSTORE_FILENAME = "network-root-truststore.jks"
val logger: Logger = LoggerFactory.getLogger("com.r3.corda.networkmanage.common.utils")
data class CertPathAndKey(val certPath: List<X509Certificate>, val key: PrivateKey) {
fun toKeyPair(): KeyPair = KeyPair(certPath[0].publicKey, key)
}
inline fun <reified T : Any> parseConfig(file: Path): T {
val config = ConfigFactory.parseFile(file.toFile(), ConfigParseOptions.defaults().setAllowMissing(true)).resolve()
logger.info(config.root().render(ConfigRenderOptions.defaults()))
return config.parseAs(UnknownConfigKeysPolicy.IGNORE::handle)
}
fun buildCertPath(certPathBytes: ByteArray): CertPath = X509CertificateFactory().delegate.generateCertPath(certPathBytes.inputStream())
fun X509KeyStore.getCertPathAndKey(alias: String, privateKeyPassword: String): CertPathAndKey {
return CertPathAndKey(getCertificateChain(alias), getPrivateKey(alias, privateKeyPassword))
}
fun initialiseSerialization() {
val context = AMQP_P2P_CONTEXT
nodeSerializationEnv = SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {
registerScheme(AMQPNetworkServicesSerializationScheme())
},
context)
}
private fun PKCS10CertificationRequest.firstAttributeValue(identifier: ASN1ObjectIdentifier): ASN1Encodable? {
return getAttributes(identifier).firstOrNull()?.attrValues?.firstOrNull()
}
/**
* Helper method to extract cert role from certificate signing request. Default to NODE_CA if not exist for backward compatibility.
*/
fun PKCS10CertificationRequest.getCertRole(): CertRole {
// Default cert role to Node_CA for backward compatibility.
val encoded = firstAttributeValue(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE))?.toASN1Primitive()?.encoded ?: return CertRole.NODE_CA
return CertRole.getInstance(encoded)
}
/**
* Helper method to extract email from certificate signing request.
*/
fun PKCS10CertificationRequest.getEmail(): String = firstAttributeValue(BCStyle.E).toString()
fun <K, V, U> Map<K, V>.join(otherMap: Map<K, U>): Map<K, Pair<V?, U?>> = (keys + otherMap.keys).map { it to Pair(get(it), otherMap[it]) }.toMap()

View File

@ -1,45 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.dev
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.nodeapi.internal.*
import net.corda.nodeapi.internal.config.parseAs
import java.io.File
import java.nio.file.Path
/**
* Holds configuration necessary for generating DEV key store and trust store.
*/
data class GeneratorConfiguration(val privateKeyPass: String = DEV_CA_PRIVATE_KEY_PASS,
val keyStorePass: String = DEV_CA_KEY_STORE_PASS,
val keyStoreFileName: String = DEV_CA_KEY_STORE_FILE,
val trustStorePass: String = DEV_CA_TRUST_STORE_PASS,
val trustStoreFileName: String = DEV_CA_TRUST_STORE_FILE,
val directory: Path = DEFAULT_DIRECTORY) {
companion object {
val DEFAULT_DIRECTORY = File("./certificates").toPath()
}
}
/**
* Parses a configuration file, which contains all the configuration - i.e. for the key store generator.
*/
fun parseParameters(configFile: Path?): GeneratorConfiguration {
return if (configFile == null) {
GeneratorConfiguration()
} else {
ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))
.resolve()
.parseAs()
}
}

View File

@ -1,87 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.dev
import com.r3.corda.networkmanage.common.configuration.ConfigFilePathArgsParser
import com.r3.corda.networkmanage.doorman.CORDA_X500_BASE
import net.corda.core.crypto.Crypto
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.apache.logging.log4j.LogManager
import java.io.File
import javax.security.auth.x500.X500Principal
import kotlin.system.exitProcess
private val logger = LogManager.getLogger("com.r3.corda.networkmanage.dev.Main")
/**
* This is an internal utility method used to generate a DEV certificate store file containing both root and doorman keys/certificates.
* Additionally, it generates a trust file containing the root certificate.
*
* Note: It can be quickly executed in IntelliJ by right-click on the main method. It will generate the keystore and the trustore
* with settings expected by node running in the dev mode. The files will be generated in the root project directory.
* Look for the 'certificates' directory.
*/
fun main(args: Array<String>) {
run(parseParameters(ConfigFilePathArgsParser().parseOrExit(*args)))
}
fun run(configuration: GeneratorConfiguration) {
configuration.run {
val keyStoreFile = File("$directory/$keyStoreFileName").toPath()
keyStoreFile.parent.createDirectories()
val keyStore = X509KeyStore.fromFile(keyStoreFile, keyStorePass, createNew = true)
checkCertificateNotInKeyStore(X509Utilities.CORDA_ROOT_CA, keyStore) { exitProcess(1) }
checkCertificateNotInKeyStore(X509Utilities.CORDA_INTERMEDIATE_CA, keyStore) { exitProcess(1) }
val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCert = X509Utilities.createSelfSignedCACertificate(
X500Principal("CN=Corda Root CA,$CORDA_X500_BASE"),
rootKeyPair)
keyStore.update {
setPrivateKey(X509Utilities.CORDA_ROOT_CA, rootKeyPair.private, listOf(rootCert), privateKeyPass)
}
logger.info("Root CA keypair and certificate stored in ${keyStoreFile.toAbsolutePath()}.")
logger.info(rootCert)
val trustStorePath = directory / trustStoreFileName
X509KeyStore.fromFile(trustStorePath, trustStorePass, createNew = true).update {
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
}
logger.info("Trust store for distribution to nodes created in $trustStorePath")
val doormanKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val cert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA,
rootCert,
rootKeyPair,
X500Principal("CN=Corda Doorman CA,$CORDA_X500_BASE"),
doormanKeyPair.public
)
keyStore.update {
setPrivateKey(X509Utilities.CORDA_INTERMEDIATE_CA, doormanKeyPair.private, listOf(cert, rootCert), privateKeyPass)
}
logger.info("Doorman CA keypair and certificate stored in ${keyStoreFile.toAbsolutePath()}.")
logger.info(cert)
}
}
private fun checkCertificateNotInKeyStore(certAlias: String, keyStore: X509KeyStore, onFail: () -> Unit) {
if (certAlias in keyStore) {
logger.info("$certAlias already exists in keystore, process will now terminate.")
logger.info(keyStore.getCertificate(certAlias))
onFail.invoke()
}
}

View File

@ -1,42 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.api.JiraRestClient
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
import net.corda.core.identity.CordaX500Name
class CrrJiraClient(restClient: JiraRestClient, projectCode: String) : JiraClient(restClient, projectCode) {
fun createCertificateRevocationRequestTicket(revocationRequest: CertificateRevocationRequestData) {
val ticketDescription = "Legal name: ${revocationRequest.legalName}\n" +
"Certificate serial number: ${revocationRequest.certificateSerialNumber}\n" +
"Revocation reason: ${revocationRequest.reason.name}\n" +
"Reporter: ${revocationRequest.reporter}\n" +
"Original CSR request ID: ${revocationRequest.certificateSigningRequestId}"
val subject = CordaX500Name.build(revocationRequest.certificate.subjectX500Principal)
val ticketSummary = if (subject.organisationUnit != null) {
"${subject.organisationUnit}, ${subject.organisation}"
} else {
subject.organisation
}
val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id)
.setProjectKey(projectCode)
.setDescription(ticketDescription)
.setSummary(ticketSummary)
.setFieldValue(requestIdField.id, revocationRequest.requestId)
val attachment = Pair("${revocationRequest.certificateSerialNumber}.cer", revocationRequest.certificate.encoded.inputStream())
createJiraTicket(revocationRequest.requestId, issue.build(), attachment)
}
}

View File

@ -1,70 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.api.JiraRestClient
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder
import com.r3.corda.networkmanage.common.utils.getCertRole
import com.r3.corda.networkmanage.common.utils.getEmail
import net.corda.core.identity.CordaX500Name
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter
import javax.security.auth.x500.X500Principal
class CsrJiraClient(restClient: JiraRestClient, projectCode: String) : JiraClient(restClient, projectCode) {
fun createCertificateSigningRequestTicket(requestData: CertificationRequestData) {
val (requestId, signingRequest) = requestData
// TODO The subject of the signing request has already been validated and parsed into a CordaX500Name. We shouldn't
// have to do it again here.
val subject = CordaX500Name.build(X500Principal(signingRequest.subject.encoded))
val ticketSummary = if (subject.organisationUnit != null) {
"${subject.organisationUnit}, ${subject.organisation}"
} else {
subject.organisation
}
val data = mapOf(
"Requested Role Type" to signingRequest.getCertRole().name,
"Common Name" to subject.commonName,
"Organisation" to subject.organisation,
"Organisation Unit" to subject.organisationUnit,
"State" to subject.state,
"Nearest City" to subject.locality,
"Country" to subject.country,
"Email" to signingRequest.getEmail(),
"X500 Name" to subject.toString())
val requestPemString = StringWriter().apply {
JcaPEMWriter(this).use {
it.writeObject(PemObject("CERTIFICATE REQUEST", signingRequest.encoded))
}
}.toString()
val ticketDescription = data.filter { it.value != null }.map { "${it.key}: ${it.value}" }.joinToString("\n") + "\n\n{code}$requestPemString{code}"
val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id)
.setProjectKey(projectCode)
.setDescription(ticketDescription)
.setSummary(ticketSummary)
.setFieldValue(requestIdField.id, requestId)
createJiraTicket(requestId, issue.build())
}
}
// TODO: Parse PKCS10 request.
data class CertificationRequestData(val requestId: String, val rawRequest: PKCS10CertificationRequest)

View File

@ -1,135 +0,0 @@
package com.r3.corda.networkmanage.doorman
import com.google.common.primitives.Booleans
import com.r3.corda.networkmanage.common.utils.ArgsParser
import joptsimple.OptionException
import joptsimple.OptionSet
import joptsimple.util.EnumConverter
import joptsimple.util.PathConverter
import joptsimple.util.PathProperties
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.utilities.days
import java.nio.file.Path
import java.time.Instant
class DoormanArgsParser : ArgsParser<DoormanCmdLineOptions>() {
private val configFileArg = optionParser
.accepts("config-file", "The path to the config file")
.withRequiredArg()
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
.required()
private val modeArg = optionParser
.accepts("mode", "Set the mode of this application")
.withRequiredArg()
.withValuesConvertedBy(object : EnumConverter<Mode>(Mode::class.java) {})
.defaultsTo(Mode.DOORMAN)
private val setNetworkParametersArg = optionParser
.accepts("set-network-parameters", "Set the network parameters using the given file. This can be for either the initial set or to schedule an update.")
.withRequiredArg()
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
private val flagDayArg = optionParser.accepts("flag-day", "Roll over the scheduled network parameters to be the current.")
private val cancelUpdateArg = optionParser.accepts("cancel-update", "Cancel the scheduled update of the network parameters.")
private val trustStorePasswordArg = optionParser
.accepts("trust-store-password", "Password for the generated network root trust store. Only applicable when operating in ${Mode.ROOT_KEYGEN} mode.")
.withRequiredArg()
override fun parse(optionSet: OptionSet): DoormanCmdLineOptions {
val configFile = try {
optionSet.valueOf(configFileArg)
} catch (e: OptionException) {
throw RuntimeException("Specified config file doesn't exist").apply {
initCause(e.cause)
}
}
val mode = try {
optionSet.valueOf(modeArg)
} catch (e: OptionException) {
throw RuntimeException(
"Unknown mode specified, valid options are [${Mode.values().joinToString(", ")}]"
).apply {
initCause(e.cause)
}
}
val setNetworkParametersFile = optionSet.valueOf(setNetworkParametersArg)
val flagDay = optionSet.has(flagDayArg)
val cancelUpdate = optionSet.has(cancelUpdateArg)
require(Booleans.countTrue(setNetworkParametersFile != null, flagDay, cancelUpdate) <= 1) {
"Only one of $setNetworkParametersArg, $flagDay and $cancelUpdate can be specified"
}
val trustStorePassword = optionSet.valueOf(trustStorePasswordArg)
return DoormanCmdLineOptions(configFile, mode, trustStorePassword, setNetworkParametersFile, flagDay, cancelUpdate)
}
}
data class DoormanCmdLineOptions(val configFile: Path,
val mode: Mode,
val trustStorePassword: String?,
val setNetworkParametersFile: Path?,
val flagDay: Boolean,
val cancelUpdate: Boolean
) {
init {
// Make sure trust store password is only specified in root keygen mode.
if (mode != Mode.ROOT_KEYGEN) {
require(trustStorePassword == null) { "Trust store password should not be specified in this mode." }
}
}
}
sealed class NetworkParametersCmd {
/**
* This is the same as [NetworkParametersConfig] but instead of using [NotaryConfig] it uses the simpler to test with
* [NotaryInfo].
*/
data class Set(val minimumPlatformVersion: Int,
val notaries: List<NotaryInfo>,
val maxMessageSize: Int,
val maxTransactionSize: Int,
val eventHorizonDays: Int,
val parametersUpdate: ParametersUpdateConfig?
) : NetworkParametersCmd() {
companion object {
fun fromConfig(config: NetworkParametersConfig): Set {
return Set(
config.minimumPlatformVersion,
config.notaries.map { it.toNotaryInfo() },
config.maxMessageSize,
config.maxTransactionSize,
config.eventHorizonDays,
config.parametersUpdate
)
}
}
fun checkCompatibility(currentNetParams: NetworkParameters) {
// TODO Comment it out when maxMessageSize is properly wired
// require(previousParameters.maxMessageSize <= newParameters.maxMessageSize) { "maxMessageSize can only increase" }
require(maxTransactionSize >= currentNetParams.maxTransactionSize) { "maxTransactionSize can only increase" }
val removedNames = currentNetParams.notaries.map { it.identity.name } - notaries.map { it.identity.name }
require(removedNames.isEmpty()) { "notaries cannot be removed: $removedNames" }
val removedKeys = currentNetParams.notaries.map { it.identity.owningKey } - notaries.map { it.identity.owningKey }
require(removedKeys.isEmpty()) { "notaries cannot be removed: $removedKeys" }
}
fun toNetworkParameters(modifiedTime: Instant, epoch: Int): NetworkParameters {
return NetworkParameters(
minimumPlatformVersion,
notaries,
maxMessageSize,
maxTransactionSize,
modifiedTime,
epoch,
// TODO: Tudor, Michal - pass the actual network parameters where we figure out how
emptyMap(),
eventHorizonDays.days
)
}
}
object FlagDay : NetworkParametersCmd()
object CancelUpdate : NetworkParametersCmd()
}

View File

@ -1,107 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.google.common.primitives.Booleans
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.config.OldConfig
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import java.net.URL
import java.nio.file.Path
import java.util.*
data class NetworkManagementServerConfig( // TODO: Move local signing to signing server.
val address: NetworkHostAndPort,
val dataSourceProperties: Properties,
@OldConfig("databaseConfig")
val database: DatabaseConfig = DatabaseConfig(),
@OldConfig("doormanConfig")
val doorman: DoormanConfig?,
@OldConfig("networkMapConfig")
val networkMap: NetworkMapConfig?,
val revocation: CertificateRevocationConfig?,
// TODO Should be part of a localSigning sub-config
val keystorePath: Path? = null,
// TODO Should be part of a localSigning sub-config
val rootStorePath: Path? = null,
val keystorePassword: String?,
// TODO Should be part of a localSigning sub-config
val caPrivateKeyPassword: String?,
// TODO Should be part of a localSigning sub-config
val rootKeystorePassword: String?,
// TODO Should be part of a localSigning sub-config
val rootPrivateKeyPassword: String?,
// TODO Should be part of a localSigning sub-config
val caCrlPath: Path? = null,
// TODO Should be part of a localSigning sub-config
val caCrlUrl: URL? = null,
// TODO Should be part of a localSigning sub-config
val emptyCrlPath: Path? = null,
// TODO Should be part of a localSigning sub-config
val emptyCrlUrl: URL? = null
) {
companion object {
// TODO: Do we really need these defaults?
val DEFAULT_APPROVE_INTERVAL = 5.seconds
val DEFAULT_SIGN_INTERVAL = 5.seconds
}
}
data class DoormanConfig(val approveAll: Boolean = false,
@OldConfig("jiraConfig")
val jira: JiraConfig? = null,
val crlEndpoint: URL? = null,
val approveInterval: Long = NetworkManagementServerConfig.DEFAULT_APPROVE_INTERVAL.toMillis()) {
init {
require(Booleans.countTrue(approveAll, jira != null) == 1) {
"Either 'approveAll' or 'jira' config settings need to be specified but not both"
}
}
}
data class CertificateRevocationConfig(val approveAll: Boolean = false,
val jira: JiraConfig? = null,
val localSigning: LocalSigning?,
val crlCacheTimeout: Long,
val caCrlPath: Path,
val emptyCrlPath: Path,
val approveInterval: Long = NetworkManagementServerConfig.DEFAULT_APPROVE_INTERVAL.toMillis()) {
init {
require(Booleans.countTrue(approveAll, jira != null) == 1) {
"Either 'approveAll' or 'jira' config settings need to be specified but not both"
}
}
data class LocalSigning(val crlUpdateInterval: Long,
val crlEndpoint: URL)
}
data class NetworkMapConfig(val cacheTimeout: Long,
// TODO: Move signing to signing server.
val signInterval: Long = NetworkManagementServerConfig.DEFAULT_SIGN_INTERVAL.toMillis())
enum class Mode {
// TODO CA_KEYGEN now also generates the network map cert, so it should be renamed.
DOORMAN,
CA_KEYGEN,
ROOT_KEYGEN
}
data class JiraConfig(
val address: String,
val projectCode: String,
val username: String,
val password: String
)

View File

@ -1,142 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.api.IssueRestClient
import com.atlassian.jira.rest.client.api.JiraRestClient
import com.atlassian.jira.rest.client.api.domain.Comment
import com.atlassian.jira.rest.client.api.domain.Field
import com.atlassian.jira.rest.client.api.domain.Issue
import com.atlassian.jira.rest.client.api.domain.IssueType
import com.atlassian.jira.rest.client.api.domain.input.IssueInput
import com.atlassian.jira.rest.client.api.domain.input.TransitionInput
import com.r3.corda.networkmanage.doorman.JiraConstant.DONE_TRANSITION_KEY
import com.r3.corda.networkmanage.doorman.JiraConstant.START_TRANSITION_KEY
import com.r3.corda.networkmanage.doorman.JiraConstant.STOP_TRANSITION_KEY
import net.corda.core.internal.ConcurrentBox
import net.corda.core.utilities.contextLogger
import org.slf4j.Logger
import java.io.InputStream
/**
* The Jira client class manages concurrent access to the [JiraRestClient] to ensure atomic read and write for some operations to prevent race condition.
*/
abstract class JiraClient(restClient: JiraRestClient, protected val projectCode: String) {
companion object {
val logger = contextLogger()
}
private val restClientLock = ConcurrentBox(restClient)
// The JIRA project must have a Request ID and reject reason field, and the Task issue type.
protected val requestIdField: Field = requireNotNull(restClient.metadataClient.fields.claim().find { it.name == "Request ID" }) { "Request ID field not found in JIRA '$projectCode'" }
protected val taskIssueType: IssueType = requireNotNull(restClient.metadataClient.issueTypes.claim().find { it.name == "Task" }) { "Task issue type field not found in JIRA '$projectCode'" }
private val rejectReasonField: Field = requireNotNull(restClient.metadataClient.fields.claim().find { it.name == "Reject Reason" }) { "Reject Reason field not found in JIRA '$projectCode'" }
private val transitions = mutableMapOf<String, Int>()
fun getApprovedRequests(): List<ApprovedRequest> {
return restClientLock.concurrent {
val issues = searchClient.searchJql("project = $projectCode AND status = Approved").claim().issues
issues.mapNotNull { issue ->
val requestId = requireNotNull(issue.getField(requestIdField.id)?.value?.toString()) { "Error processing request '${issue.key}' : RequestId cannot be null." }
// Issue retrieved via search doesn't contain change logs.
val fullIssue = issueClient.getIssue(issue.key, listOf(IssueRestClient.Expandos.CHANGELOG)).claim()
val approvedBy = fullIssue.changelog?.last { it.items.any { it.field == "status" && it.toString == "Approved" } }
ApprovedRequest(requestId, approvedBy?.author?.displayName ?: "Unknown")
}
}
}
fun getRejectedRequests(): List<RejectedRequest> {
return restClientLock.concurrent {
val issues = searchClient.searchJql("project = $projectCode AND status = Rejected").claim().issues
issues.mapNotNull { issue ->
val requestId = requireNotNull(issue.getField(requestIdField.id)?.value?.toString()) { "Error processing request '${issue.key}' : RequestId cannot be null." }
val rejectedReason = issue.getField(rejectReasonField.id)?.value?.toString()
// Issue retrieved via search doesn't contain comments.
val fullIssue = issueClient.getIssue(issue.key, listOf(IssueRestClient.Expandos.CHANGELOG)).claim()
val rejectedBy = fullIssue.changelog?.last { it.items.any { it.field == "status" && it.toString == "Rejected" } }
RejectedRequest(requestId, rejectedBy?.author?.displayName ?: "Unknown", rejectedReason)
}
}
}
fun updateRejectedRequest(requestId: String) {
restClientLock.exclusive {
val issue = requireNotNull(getIssueById(requestId)) { "Issue with the `request ID` = $requestId does not exist." }
// Move status to in progress.
issueClient.transition(issue, TransitionInput(getTransitionId(START_TRANSITION_KEY, issue))).fail { logger.error("Error processing request '${issue.key}' : Exception when transiting JIRA status.", it) }.claim()
// Move status to stopped.
issueClient.transition(issue, TransitionInput(getTransitionId(STOP_TRANSITION_KEY, issue))).fail { logger.error("Error processing request '${issue.key}' : Exception when transiting JIRA status.", it) }.claim()
issueClient.addComment(issue.commentsUri, Comment.valueOf("Request cancelled by doorman.")).claim()
}
}
private fun JiraRestClient.getIssueById(requestId: String): Issue? {
// Jira only support ~ (contains) search for custom textfield.
return searchClient.searchJql("'Request ID' ~ $requestId").claim().issues.firstOrNull()
}
private fun getTransitionId(transitionKey: String, issue: Issue): Int {
return transitions.computeIfAbsent(transitionKey, { key ->
restClientLock.concurrent {
issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == key }.id
}
})
}
protected fun createJiraTicket(requestId: String, issue: IssueInput, attachment: Pair<String, InputStream>? = null) {
restClientLock.exclusive {
if (getIssueById(requestId) != null) {
logger.warn("There is already a ticket corresponding to request Id $requestId, not creating a new one.")
return
}
// This will block until the issue is created.
issueClient.createIssue(issue).fail { logger.error("Exception when creating JIRA issue for request: '$requestId'.", it) }.claim()
attachment?.let { addAttachment(requestId, it) }
}
}
fun transitRequestStatusToDone(requestId: String, attachment: Pair<String, InputStream>? = null) {
restClientLock.exclusive {
val issue = requireNotNull(getIssueById(requestId)) { "Cannot find the JIRA ticket `request ID` = $requestId" }
issueClient.transition(issue, TransitionInput(getTransitionId(DONE_TRANSITION_KEY, issue))).fail { logger.error("Exception when transiting JIRA status.", it) }.claim()
attachment?.let { addAttachment(requestId, it) }
}
}
private fun JiraRestClient.addAttachment(requestId: String, attachment: Pair<String, InputStream>) {
val createdIssue = checkNotNull(getIssueById(requestId)) { "Missing the JIRA ticket for the request ID: $requestId" }
issueClient.addAttachment(createdIssue.attachmentsUri, attachment.second, attachment.first)
.fail { logger.error("Error processing request '${createdIssue.key}' : Exception when uploading attachment to JIRA.", it) }.claim()
}
}
data class ApprovedRequest(val requestId: String, val approvedBy: String)
data class RejectedRequest(val requestId: String, val rejectedBy: String, val reason: String?)
inline fun <T : Any> Iterable<T>.forEachWithExceptionLogging(logger: Logger, action: (T) -> Unit) {
for (element in this) {
try {
action(element)
} catch (e: Exception) {
logger.error("Error while processing an element: $element", e)
}
}
}
object JiraConstant{
const val DONE_TRANSITION_KEY = "Done"
const val START_TRANSITION_KEY = "Start Progress"
const val STOP_TRANSITION_KEY = "Stop Progress"
}

View File

@ -1,129 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.jcabi.manifests.Manifests
import com.r3.corda.networkmanage.common.utils.*
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.crypto.Crypto
import net.corda.core.internal.exists
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.slf4j.LoggerFactory
import java.time.Instant
import kotlin.system.exitProcess
private val logger = LoggerFactory.getLogger("com.r3.corda.networkmanage.doorman")
fun main(args: Array<String>) {
Crypto.registerProviders() // Required to register Providers first thing on boot.
if (Manifests.exists("Doorman-Version")) {
println("Version: ${Manifests.read("Doorman-Version")}")
}
initialiseSerialization()
val cmdLineOptions = DoormanArgsParser().parseOrExit(*args, printHelpOn = System.err)
val config = parseConfig<NetworkManagementServerConfig>(cmdLineOptions.configFile)
logger.info("Running in ${cmdLineOptions.mode} mode")
when (cmdLineOptions.mode) {
Mode.ROOT_KEYGEN -> {
val emptyCrlPath = requireNotNull(config.emptyCrlPath) { "emptyCrlPath needs to be specified" }
val emptyCrlUrl = requireNotNull(config.emptyCrlUrl) { "emptyCrlUrl needs to be specified" }
val caCrlPath = requireNotNull(config.caCrlPath) { "caCrlPath needs to be specified" }
val caCrlUrl = requireNotNull(config.caCrlUrl) { "caCrlUrl needs to be specified" }
val rootCertificateAndKeyPair = rootKeyGenMode(cmdLineOptions, config)
createEmptyCrls(rootCertificateAndKeyPair, emptyCrlPath, emptyCrlUrl, caCrlPath, caCrlUrl)
}
Mode.CA_KEYGEN -> caKeyGenMode(config)
Mode.DOORMAN -> doormanMode(cmdLineOptions, config)
}
}
data class NetworkMapStartParams(val signer: LocalSigner?, val config: NetworkMapConfig)
data class NetworkManagementServerStatus(var serverStartTime: Instant = Instant.now(), var lastRequestCheckTime: Instant? = null)
private fun processKeyStore(config: NetworkManagementServerConfig): Pair<CertPathAndKey, LocalSigner>? {
if (config.keystorePath == null) return null
if (!config.keystorePath.exists()) {
println("Could not find keystore: ${config.keystorePath}. You need to create it first or point to the correct location. Please consult the documentation.")
exitProcess(0)
}
// Get password from console if not in config.
val keyStorePassword = config.keystorePassword ?: readPassword("Key store password: ")
val privateKeyPassword = config.caPrivateKeyPassword ?: readPassword("Private key password: ")
val keyStore = X509KeyStore.fromFile(config.keystorePath, keyStorePassword)
val csrCertPathAndKey = keyStore.getCertPathAndKey(X509Utilities.CORDA_INTERMEDIATE_CA, privateKeyPassword)
val networkMapSigner = LocalSigner(keyStore.getCertificateAndKeyPair(CORDA_NETWORK_MAP, privateKeyPassword))
return Pair(csrCertPathAndKey, networkMapSigner)
}
private fun rootKeyGenMode(cmdLineOptions: DoormanCmdLineOptions, config: NetworkManagementServerConfig): CertificateAndKeyPair {
return generateRootKeyPair(
requireNotNull(config.rootStorePath) { "The 'rootStorePath' parameter must be specified when generating keys!" },
config.rootKeystorePassword,
config.rootPrivateKeyPassword,
cmdLineOptions.trustStorePassword
)
}
private fun caKeyGenMode(config: NetworkManagementServerConfig) {
generateSigningKeyPairs(
requireNotNull(config.keystorePath) { "The 'keystorePath' parameter must be specified when generating keys!" },
requireNotNull(config.rootStorePath) { "The 'rootStorePath' parameter must be specified when generating keys!" },
config.rootKeystorePassword,
config.rootPrivateKeyPassword,
config.keystorePassword,
config.caPrivateKeyPassword,
config.caCrlUrl
)
}
private fun doormanMode(cmdLineOptions: DoormanCmdLineOptions, config: NetworkManagementServerConfig) {
val networkManagementServer = NetworkManagementServer(config.dataSourceProperties, config.database, config.doorman, config.revocation)
val networkParametersCmd = when {
cmdLineOptions.setNetworkParametersFile != null ->
networkManagementServer.netParamsUpdateHandler.loadParametersFromFile(cmdLineOptions.setNetworkParametersFile)
cmdLineOptions.flagDay -> NetworkParametersCmd.FlagDay
cmdLineOptions.cancelUpdate -> NetworkParametersCmd.CancelUpdate
else -> null
}
if (networkParametersCmd == null) {
// TODO: move signing to signing server.
val csrAndNetworkMap = processKeyStore(config)
if (csrAndNetworkMap != null) {
logger.info("Starting network management services with local signing")
}
val networkMapStartParams = config.networkMap?.let {
NetworkMapStartParams(csrAndNetworkMap?.second, it)
}
networkManagementServer.start(
config.address,
csrAndNetworkMap?.first,
networkMapStartParams)
Runtime.getRuntime().addShutdownHook(object : Thread("ShutdownHook") {
override fun run() {
networkManagementServer.close()
}
})
} else {
networkManagementServer.use {
it.netParamsUpdateHandler.processNetworkParameters(networkParametersCmd)
}
}
}

View File

@ -1,197 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory
import com.r3.corda.networkmanage.common.persistence.*
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.doorman.signer.*
import com.r3.corda.networkmanage.doorman.webservice.*
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import org.apache.commons.io.FileUtils
import java.io.Closeable
import java.net.URI
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class NetworkManagementServer(dataSourceProperties: Properties,
databaseConfig: DatabaseConfig,
private val doormanConfig: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
private val revocationConfig: CertificateRevocationConfig?) : Closeable {
companion object {
private val logger = contextLogger()
}
private val closeActions = mutableListOf<() -> Unit>()
private val database = configureDatabase(dataSourceProperties, databaseConfig).also { closeActions += it::close }
private val networkMapStorage = PersistentNetworkMapStorage(database)
private val nodeInfoStorage = PersistentNodeInfoStorage(database)
private val csrStorage = PersistentCertificateSigningRequestStorage(database).let {
if (doormanConfig?.approveAll ?: false) {
ApproveAllCertificateSigningRequestStorage(it)
} else {
it
}
}
val netParamsUpdateHandler = ParametersUpdateHandler(csrStorage, networkMapStorage)
lateinit var hostAndPort: NetworkHostAndPort
override fun close() {
logger.info("Closing server...")
for (closeAction in closeActions.reversed()) {
try {
closeAction()
} catch (e: Exception) {
logger.warn("Disregarding exception thrown during close", e)
}
}
}
private fun getNetworkMapService(config: NetworkMapConfig, signer: LocalSigner?): NetworkMapWebService {
logger.info("Starting Network Map server.")
val localNetworkMapSigner = signer?.let { NetworkMapSigner(networkMapStorage, it) }
val latestParameters = networkMapStorage.getLatestNetworkParameters()?.networkParameters ?: throw IllegalStateException("No network parameters were found. Please upload new network parameters before starting network map service")
logger.info("Starting network map service with latest network parameters: $latestParameters")
localNetworkMapSigner?.signAndPersistNetworkParameters(latestParameters)
if (localNetworkMapSigner != null) {
logger.info("Starting background worker for signing the network map using the local key store")
val scheduledExecutor = Executors.newScheduledThreadPool(1)
scheduledExecutor.scheduleAtFixedRate({
try {
localNetworkMapSigner.signNetworkMaps()
} catch (e: Exception) {
// Log the error and carry on.
logger.error("Unable to sign network map", e)
}
}, config.signInterval, config.signInterval, TimeUnit.MILLISECONDS)
closeActions += scheduledExecutor::shutdown
}
return NetworkMapWebService(nodeInfoStorage, networkMapStorage, PersistentCertificateSigningRequestStorage(database), config)
}
private fun getDoormanService(config: DoormanConfig,
csrCertPathAndKey: CertPathAndKey?,
serverStatus: NetworkManagementServerStatus): RegistrationWebService {
logger.info("Starting Doorman server.")
val jiraConfig = config.jira
val requestProcessor = if (jiraConfig != null) {
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
val jiraClient = CsrJiraClient(jiraWebAPI, jiraConfig.projectCode)
JiraCsrHandler(jiraClient, csrStorage, DefaultCsrHandler(csrStorage, csrCertPathAndKey, config.crlEndpoint))
} else {
DefaultCsrHandler(csrStorage, csrCertPathAndKey, config.crlEndpoint)
}
val scheduledExecutor = Executors.newScheduledThreadPool(1)
val approvalThread = Runnable {
try {
serverStatus.lastRequestCheckTime = Instant.now()
// Process Jira approved tickets.
requestProcessor.processRequests()
} catch (e: Exception) {
// Log the error and carry on.
logger.error("Error encountered when approving request.", e)
}
}
scheduledExecutor.scheduleAtFixedRate(approvalThread, config.approveInterval, config.approveInterval, TimeUnit.MILLISECONDS)
closeActions += scheduledExecutor::shutdown
return RegistrationWebService(requestProcessor, Duration.ofMillis(config.approveInterval))
}
private fun getRevocationServices(config: CertificateRevocationConfig,
csrCertPathAndKeyPair: CertPathAndKey?): Pair<CertificateRevocationRequestWebService, CertificateRevocationListWebService> {
logger.info("Starting Revocation server.")
val crrStorage = PersistentCertificateRevocationRequestStorage(database).let {
if (config.approveAll) {
ApproveAllCertificateRevocationRequestStorage(it)
} else {
it
}
}
val crlStorage = PersistentCertificateRevocationListStorage(database)
val crlHandler = csrCertPathAndKeyPair?.let {
LocalCrlHandler(crrStorage,
crlStorage,
CertificateAndKeyPair(it.certPath[0], it.toKeyPair()),
Duration.ofMillis(config.localSigning!!.crlUpdateInterval),
config.localSigning.crlEndpoint)
}
val jiraConfig = config.jira
val crrHandler = if (jiraConfig != null) {
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
val jiraClient = CrrJiraClient(jiraWebAPI, jiraConfig.projectCode)
JiraCrrHandler(jiraClient, crrStorage, crlHandler)
} else {
DefaultCrrHandler(crrStorage, crlHandler)
}
val scheduledExecutor = Executors.newScheduledThreadPool(1)
val approvalThread = Runnable {
try {
// Process Jira approved tickets.
crrHandler.processRequests()
} catch (e: Exception) {
// Log the error and carry on.
logger.error("Error encountered when approving request.", e)
}
}
scheduledExecutor.scheduleAtFixedRate(approvalThread, config.approveInterval, config.approveInterval, TimeUnit.MILLISECONDS)
closeActions += scheduledExecutor::shutdown
// TODO start socket server
return Pair(
CertificateRevocationRequestWebService(crrHandler),
CertificateRevocationListWebService(
crlStorage,
FileUtils.readFileToByteArray(config.caCrlPath.toFile()),
FileUtils.readFileToByteArray(config.emptyCrlPath.toFile()),
Duration.ofMillis(config.crlCacheTimeout)))
}
fun start(hostAndPort: NetworkHostAndPort,
csrCertPathAndKey: CertPathAndKey?,
startNetworkMap: NetworkMapStartParams?
) {
val services = mutableListOf<Any>()
val serverStatus = NetworkManagementServerStatus()
startNetworkMap?.let { services += getNetworkMapService(it.config, it.signer) }
doormanConfig?.let { services += getDoormanService(it, csrCertPathAndKey, serverStatus) }
revocationConfig?.let {
val revocationServices = getRevocationServices(it, csrCertPathAndKey)
services += revocationServices.first
services += revocationServices.second
}
require(services.isNotEmpty()) { "No service created, please provide at least one service config." }
// TODO: use mbean to expose audit data?
services += MonitoringWebService(serverStatus)
val webServer = NetworkManagementWebServer(hostAndPort, *services.toTypedArray())
webServer.start()
closeActions += webServer::close
this.hostAndPort = webServer.hostAndPort
}
}

View File

@ -1,151 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.common.utils.NETWORK_ROOT_TRUSTSTORE_FILENAME
import com.r3.corda.networkmanage.common.utils.createSignedCrl
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import net.corda.nodeapi.internal.crypto.X509Utilities.createCertificate
import net.corda.nodeapi.internal.crypto.X509Utilities.createSelfSignedCACertificate
import org.apache.commons.io.FileUtils
import java.net.URL
import java.nio.file.Path
import javax.security.auth.x500.X500Principal
import kotlin.system.exitProcess
// TODO The cert subjects need to be configurable
const val CORDA_X500_BASE = "O=R3 HoldCo LLC,OU=Corda,L=New York,C=US"
/** Read password from console, do a readLine instead if console is null (e.g. when debugging in IDE). */
internal fun readPassword(fmt: String): String {
return if (System.console() != null) {
String(System.console().readPassword(fmt))
} else {
print(fmt)
readLine() ?: ""
}
}
// Keygen utilities.
fun generateRootKeyPair(rootStoreFile: Path, rootKeystorePass: String?, rootPrivateKeyPass: String?, networkRootTrustPass: String?): CertificateAndKeyPair {
println("Generating Root CA keypair and certificate.")
// Get password from console if not in config.
val rootKeystorePassword = rootKeystorePass ?: readPassword("Root Keystore Password: ")
// Ensure folder exists.
rootStoreFile.parent.createDirectories()
val rootStore = X509KeyStore.fromFile(rootStoreFile, rootKeystorePassword, createNew = true)
val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root Private Key Password: ")
if (CORDA_ROOT_CA in rootStore) {
println("$CORDA_ROOT_CA already exists in keystore, process will now terminate.")
println(rootStore.getCertificate(CORDA_ROOT_CA))
exitProcess(1)
}
val selfSignKey = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
// TODO Make the cert subject configurable
val rootCert = createSelfSignedCACertificate(
X500Principal("CN=Corda Root CA,$CORDA_X500_BASE"),
selfSignKey)
rootStore.update {
setPrivateKey(CORDA_ROOT_CA, selfSignKey.private, listOf(rootCert), rootPrivateKeyPassword)
}
val trustStorePath = (rootStoreFile.parent / "distribute-nodes").createDirectories() / NETWORK_ROOT_TRUSTSTORE_FILENAME
val networkRootTrustPassword = networkRootTrustPass ?: readPassword("Network Root Trust Store Password: ")
X509KeyStore.fromFile(trustStorePath, networkRootTrustPassword, createNew = true).update {
setCertificate(CORDA_ROOT_CA, rootCert)
}
println("Trust store for distribution to nodes created in $trustStorePath")
println("Root CA keypair and certificate stored in ${rootStoreFile.toAbsolutePath()}.")
println(rootCert)
return CertificateAndKeyPair(rootCert, selfSignKey)
}
fun createEmptyCrls(rootCertificateAndKeyPair: CertificateAndKeyPair, emptyCrlPath: Path, emptyCrlUrl: URL, caCrlPath: Path, caCrlUrl: URL) {
val rootCert = rootCertificateAndKeyPair.certificate
val rootKey = rootCertificateAndKeyPair.keyPair.private
val emptyCrl = createSignedCrl(rootCert, emptyCrlUrl, 3650.days, LocalSigner(rootKey, rootCert), emptyList(), true)
FileUtils.writeByteArrayToFile(emptyCrlPath.toFile(), emptyCrl.encoded)
val caCrl = createSignedCrl(rootCert, caCrlUrl, 3650.days, LocalSigner(rootKey, rootCert), emptyList())
FileUtils.writeByteArrayToFile(caCrlPath.toFile(), caCrl.encoded)
println("Empty CRL: $emptyCrl")
println("CA CRL: $caCrl")
println("Root signed empty and CA CRL files created in $emptyCrlPath and $caCrlPath respectively")
}
fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystorePass: String?, rootPrivateKeyPass: String?, keystorePass: String?, caPrivateKeyPass: String?, caCrlUrl: URL?) {
println("Generating intermediate and network map key pairs and certificates using root key store $rootStoreFile.")
// Get password from console if not in config.
val rootKeystorePassword = rootKeystorePass ?: readPassword("Root key store password: ")
val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root private key password: ")
val rootKeyStore = X509KeyStore.fromFile(rootStoreFile, rootKeystorePassword)
val rootKeyPairAndCert = rootKeyStore.getCertificateAndKeyPair(CORDA_ROOT_CA, rootPrivateKeyPassword)
val keyStorePassword = keystorePass ?: readPassword("Key store Password: ")
val privateKeyPassword = caPrivateKeyPass ?: readPassword("Private key Password: ")
// Ensure folder exists.
keystoreFile.parent.createDirectories()
val keyStore = X509KeyStore.fromFile(keystoreFile, keyStorePassword, createNew = true)
fun storeCertIfAbsent(alias: String, certificateType: CertificateType, subject: X500Principal, signatureScheme: SignatureScheme) {
if (alias in keyStore) {
println("$alias already exists in keystore:")
println(keyStore.getCertificate(alias))
return
}
val keyPair = Crypto.generateKeyPair(signatureScheme)
val cert = createCertificate(
certificateType,
rootKeyPairAndCert.certificate,
rootKeyPairAndCert.keyPair,
subject,
keyPair.public,
crlDistPoint = caCrlUrl?.toString()
)
keyStore.update {
setPrivateKey(alias, keyPair.private, listOf(cert, rootKeyPairAndCert.certificate), privateKeyPassword)
}
println("$certificateType key pair and certificate stored in $keystoreFile.")
println(cert)
}
storeCertIfAbsent(
CORDA_INTERMEDIATE_CA,
CertificateType.INTERMEDIATE_CA,
X500Principal("CN=Corda Doorman CA,$CORDA_X500_BASE"),
DEFAULT_TLS_SIGNATURE_SCHEME)
storeCertIfAbsent(
CORDA_NETWORK_MAP,
CertificateType.NETWORK_MAP,
X500Principal("CN=Corda Network Map,$CORDA_X500_BASE"),
Crypto.EDDSA_ED25519_SHA512)
}

View File

@ -1,69 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import java.io.Closeable
import java.net.InetSocketAddress
/**
* NetworkManagementWebServer runs on Jetty server and provides service via http.
*/
class NetworkManagementWebServer(hostAndPort: NetworkHostAndPort, private vararg val webServices: Any) : Closeable {
companion object {
val logger = contextLogger()
}
private val server: Server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
handler = HandlerCollection().apply {
addHandler(buildServletContextHandler())
}
}
val hostAndPort: NetworkHostAndPort
get() = server.connectors.mapNotNull { it as? ServerConnector }
.map { NetworkHostAndPort(it.host, it.localPort) }
.first()
override fun close() {
logger.info("Shutting down network management web services...")
server.stop()
server.join()
}
fun start() {
logger.info("Starting network management web services...")
server.start()
logger.info("Network management web services started on $hostAndPort with ${webServices.map { it.javaClass.simpleName }}")
println("Network management web services started on $hostAndPort with ${webServices.map { it.javaClass.simpleName }}")
}
private fun buildServletContextHandler(): ServletContextHandler {
return ServletContextHandler().apply {
contextPath = "/"
val resourceConfig = ResourceConfig().apply {
// Add your API provider classes (annotated for JAX-RS) here
webServices.forEach { register(it) }
}
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start
addServlet(jerseyServlet, "/*")
}
}
}

View File

@ -1,58 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman
import com.typesafe.config.ConfigFactory
import net.corda.core.internal.readObject
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.nodeapi.internal.SignedNodeInfo
import java.nio.file.Path
import java.time.Instant
import java.time.Duration
/**
* Data class representing a [NotaryInfo] which can be easily parsed by a typesafe [ConfigFactory].
* @property notaryNodeInfoFile path to the node info file of the notary node.
* @property validating whether the notary is validating
*/
data class NotaryConfig(private val notaryNodeInfoFile: Path,
private val validating: Boolean) {
val nodeInfo by lazy { toNodeInfo() }
fun toNotaryInfo(): NotaryInfo {
// It is always the last identity (in the list of identities) that corresponds to the notary identity.
// In case of a single notary, the list has only one element. In case of distributed notaries the list has
// two items and the second one corresponds to the notary identity.
return NotaryInfo(nodeInfo.legalIdentities.last(), validating)
}
private fun toNodeInfo(): NodeInfo = notaryNodeInfoFile.readObject<SignedNodeInfo>().verified()
}
data class ParametersUpdateConfig(val description: String, val updateDeadline: Instant) {
init {
val now = Instant.now()
require(updateDeadline == now || updateDeadline.isAfter(now)) { "The updateDeadline time must be in the future." }
}
}
/**
* Data class containing the fields from [NetworkParameters] which can be read at start-up time from doorman.
* It is a proper subset of [NetworkParameters] except for the [notaries] field which is replaced by a list of
* [NotaryConfig] which is parsable.
*/
data class NetworkParametersConfig(val minimumPlatformVersion: Int,
val notaries: List<NotaryConfig>,
val maxMessageSize: Int,
val maxTransactionSize: Int,
val eventHorizonDays: Int,
val parametersUpdate: ParametersUpdateConfig?)

View File

@ -1,123 +0,0 @@
package com.r3.corda.networkmanage.doorman
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.core.internal.CertRole
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.crypto.x509Certificates
import java.nio.file.Path
import java.time.Instant
class ParametersUpdateHandler(val csrStorage: CertificateSigningRequestStorage, val networkMapStorage: NetworkMapStorage) {
companion object {
private val logger = contextLogger()
}
fun loadParametersFromFile(file: Path): NetworkParametersCmd.Set {
val netParamsConfig = ConfigFactory.parseFile(file.toFile(), ConfigParseOptions.defaults())
.parseAs<NetworkParametersConfig>()
checkNotaryCertificates(netParamsConfig.notaries.map { it.nodeInfo })
return NetworkParametersCmd.Set.fromConfig(netParamsConfig)
}
fun processNetworkParameters(networkParametersCmd: NetworkParametersCmd) {
when (networkParametersCmd) {
is NetworkParametersCmd.Set -> handleSetNetworkParameters(networkParametersCmd)
NetworkParametersCmd.FlagDay -> handleFlagDay()
NetworkParametersCmd.CancelUpdate -> handleCancelUpdate()
}
}
private fun checkNotaryCertificates(notaryNodeInfos: List<NodeInfo>) {
notaryNodeInfos.forEach { notaryInfo ->
val cert = notaryInfo.legalIdentitiesAndCerts.last().certPath.x509Certificates.find {
val certRole = CertRole.extract(it)
certRole == CertRole.SERVICE_IDENTITY || certRole == CertRole.NODE_CA
}
cert ?: throw IllegalArgumentException("The notary certificate path does not contain SERVICE_IDENTITY or NODE_CA role in it")
csrStorage.getValidCertificatePath(cert.publicKey)
?: throw IllegalArgumentException("Notary with node info: $notaryInfo is not registered with the doorman")
}
}
private fun handleSetNetworkParameters(setNetParams: NetworkParametersCmd.Set) {
logger.info("maxMessageSize is not currently wired in the nodes")
val activeMap = networkMapStorage.getNetworkMaps().publicNetworkMap
val activeNetParams = activeMap?.networkParameters?.networkParameters
// Setting initial parameters case.
if (activeNetParams == null) {
require(setNetParams.parametersUpdate == null) {
"'parametersUpdate' specified in network parameters file but there are no network parameters to update"
}
val initialNetParams = setNetParams.toNetworkParameters(modifiedTime = Instant.now(), epoch = 1)
logger.info("Saving initial network parameters to be signed:\n$initialNetParams")
networkMapStorage.saveNetworkParameters(initialNetParams, null)
println("Saved initial network parameters to be signed:\n$initialNetParams")
} else { // An update.
val parametersUpdate = requireNotNull(setNetParams.parametersUpdate) {
"'parametersUpdate' not specified in network parameters file but there is already an active set of network parameters"
}
setNetParams.checkCompatibility(activeNetParams)
val latestNetParams = checkNotNull(networkMapStorage.getLatestNetworkParameters()?.networkParameters) {
"Something has gone wrong! We have an active set of network parameters ($activeNetParams) but apparently no latest network parameters!"
}
// It's not necessary that latestNetParams is the current active network parameters. It can be the network
// parameters from a previous update attempt which hasn't activated yet. We still take the epoch value for this
// new set from latestNetParams to make sure the advertised update attempts have incrementing epochs.
// This has the implication that *active* network parameters may have gaps in their epochs.
val newNetParams = setNetParams.toNetworkParameters(modifiedTime = Instant.now(), epoch = latestNetParams.epoch + 1)
val activeUpdate = activeMap.networkMap.parametersUpdate
if (sameNetworkParameters(latestNetParams, newNetParams) && activeUpdate != null) {
// TODO We don't have cancel event propagation on client side.
throw IllegalArgumentException("New network parameters are the same as the latest ones and there is an update scheduled: $activeUpdate\n" +
"If you want to just change updateDeadline or update description - cancel old update first.")
}
logger.info("Enabling update to network parameters:\n$newNetParams\n$parametersUpdate")
networkMapStorage.saveNewParametersUpdate(newNetParams, parametersUpdate.description, parametersUpdate.updateDeadline)
logger.info("Update enabled")
println("Enabled update to network parameters:\n$newNetParams\n$parametersUpdate")
}
}
private fun sameNetworkParameters(params1: NetworkParameters, params2: NetworkParameters): Boolean {
return params1.copy(epoch = 1, modifiedTime = Instant.MAX) == params2.copy(epoch = 1, modifiedTime = Instant.MAX)
}
private fun handleFlagDay() {
val parametersUpdate = checkNotNull(networkMapStorage.getCurrentParametersUpdate()) {
"No network parameters updates are scheduled"
}
check(Instant.now() >= parametersUpdate.updateDeadline) {
"Update deadline of ${parametersUpdate.updateDeadline} hasn't passed yet"
}
val latestNetParamsEntity = networkMapStorage.getLatestNetworkParameters()
check(parametersUpdate.networkParameters.hash == networkMapStorage.getLatestNetworkParameters()?.hash) {
"The latest network parameters is not the scheduled one:\n${latestNetParamsEntity?.networkParameters}\n${parametersUpdate.toParametersUpdate()}"
}
val activeNetParams = networkMapStorage.getNetworkMaps().publicNetworkMap?.networkParameters
check(parametersUpdate.networkParameters.isSigned) {
"Parameters we are trying to switch to haven't been signed yet"
}
logger.info("""Flag day has occurred, however the new network parameters won't be active until the new network map is signed.
From: ${activeNetParams?.networkParameters}
To: ${parametersUpdate.networkParameters.networkParameters}""")
networkMapStorage.setParametersUpdateStatus(parametersUpdate, UpdateStatus.FLAG_DAY)
}
private fun handleCancelUpdate() {
val parametersUpdate = checkNotNull(networkMapStorage.getCurrentParametersUpdate()) {
"No network parameters updates are scheduled"
}
logger.info("""Cancelling parameters update: ${parametersUpdate.toParametersUpdate()}.
However, the network map will continue to advertise this update until the new one is signed.""")
networkMapStorage.setParametersUpdateStatus(parametersUpdate, UpdateStatus.CANCELLED)
println("Done with cancel update")
}
}

View File

@ -1,59 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.signer.CertificateRevocationListSigner
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import java.net.URL
import java.time.Duration
class LocalCrlHandler(private val crrStorage: CertificateRevocationRequestStorage,
private val crlStorage: CertificateRevocationListStorage,
issuerCertAndKey: CertificateAndKeyPair,
crlUpdateInterval: Duration,
crlEndpoint: URL) {
private companion object {
private val logger = contextLogger()
}
private val crlSigner = CertificateRevocationListSigner(
crlStorage,
issuerCertAndKey.certificate,
crlUpdateInterval,
crlEndpoint,
LocalSigner(issuerCertAndKey))
fun signCrl() {
if (crlStorage.getCertificateRevocationList(CrlIssuer.DOORMAN) == null) {
val crl = crlSigner.createSignedCRL(emptyList(), emptyList(), DOORMAN_SIGNATURE)
logger.info("Saving a new empty CRL: $crl")
return
}
logger.info("Executing CRL signing...")
val approvedRequests = crrStorage.getRevocationRequests(RequestStatus.APPROVED)
logger.debug("Approved certificate revocation requests retrieved: $approvedRequests")
if (approvedRequests.isEmpty()) {
// Nothing to add to the current CRL
logger.debug("There are no APPROVED certificate revocation requests. Aborting CRL signing.")
return
}
val currentRequests = crrStorage.getRevocationRequests(RequestStatus.DONE)
logger.debug("Existing certificate revocation requests retrieved: $currentRequests")
val crl = crlSigner.createSignedCRL(approvedRequests, currentRequests, DOORMAN_SIGNATURE)
logger.info("New CRL signed: $crl")
}
}

View File

@ -1,27 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
interface CrrHandler {
fun processRequests()
fun saveRevocationRequest(request: CertificateRevocationRequest): String
}
class DefaultCrrHandler(private val crrStorage: CertificateRevocationRequestStorage,
private val localCrlHandler: LocalCrlHandler?) : CrrHandler {
override fun saveRevocationRequest(request: CertificateRevocationRequest): String = crrStorage.saveRevocationRequest(request)
override fun processRequests() {
localCrlHandler?.signCrl()
}
}

View File

@ -1,84 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.common.utils.getCertRole
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.certificateType
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import java.net.URL
import java.security.cert.CertPath
import javax.security.auth.x500.X500Principal
interface CsrHandler {
fun saveRequest(rawRequest: PKCS10CertificationRequest): String
fun processRequests()
fun getResponse(requestId: String): CertificateResponse
}
class DefaultCsrHandler(private val storage: CertificateSigningRequestStorage,
private val csrCertPathAndKey: CertPathAndKey?,
private val crlDistributionPoint: URL? = null) : CsrHandler {
override fun processRequests() {
if (csrCertPathAndKey == null) return
storage.getRequests(RequestStatus.APPROVED).forEach {
val nodeCertPath = createSignedNodeCertificate(it.request, csrCertPathAndKey)
// Since Doorman is deployed in the auto-signing mode (i.e. signer != null),
// we use DOORMAN_SIGNATURE as the signer.
storage.putCertificatePath(it.requestId, nodeCertPath, DOORMAN_SIGNATURE)
}
}
override fun saveRequest(rawRequest: PKCS10CertificationRequest): String = storage.saveRequest(rawRequest)
override fun getResponse(requestId: String): CertificateResponse {
val response = storage.getRequest(requestId)
return when (response?.status) {
RequestStatus.NEW, RequestStatus.APPROVED, RequestStatus.TICKET_CREATED, null -> CertificateResponse.NotReady
RequestStatus.REJECTED -> CertificateResponse.Unauthorised(response.remark ?: "Unknown reason")
RequestStatus.DONE -> CertificateResponse.Ready(requireNotNull(response.certData?.certPath) { "Certificate should not be null." })
}
}
private fun createSignedNodeCertificate(certificationRequest: PKCS10CertificationRequest,
csrCertPathAndKey: CertPathAndKey): CertPath {
// The sub certs issued by the client must satisfy this directory name (or legal name in Corda) constraints,
// sub certs' directory name must be within client CA's name's subtree,
// please see [sun.security.x509.X500Name.isWithinSubtree()] for more information.
// We assume all attributes in the subject name has been checked prior approval.
// TODO: add validation to subject name.
val request = JcaPKCS10CertificationRequest(certificationRequest)
val certRole = request.getCertRole()
val nameConstraints = NameConstraints(
arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))),
arrayOf())
val nodeCaCert = X509Utilities.createCertificate(
certRole.certificateType,
csrCertPathAndKey.certPath[0],
csrCertPathAndKey.toKeyPair(),
X500Principal(request.subject.encoded),
request.publicKey,
nameConstraints = nameConstraints,
crlDistPoint = crlDistributionPoint?.toString())
return X509CertificateFactory().generateCertPath(listOf(nodeCaCert) + csrCertPathAndKey.certPath)
}
}

View File

@ -1,84 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.doorman.*
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
class JiraCrrHandler(private val jiraClient: CrrJiraClient,
private val crrStorage: CertificateRevocationRequestStorage,
private val localCrlHandler: LocalCrlHandler?) : CrrHandler {
private companion object {
val logger = contextLogger()
}
override fun saveRevocationRequest(request: CertificateRevocationRequest): String {
try {
val requestId = crrStorage.saveRevocationRequest(request)
val requestData = crrStorage.getRevocationRequest(requestId)
requestData ?: throw IllegalStateException("Request $requestId does not exist.")
jiraClient.createCertificateRevocationRequestTicket(requestData)
crrStorage.markRequestTicketCreated(requestId)
return requestId
} catch (e: Exception) {
logger.error("There was an error while creating JIRA tickets", e)
throw e
}
}
override fun processRequests() {
createTickets()
val (approvedRequests, rejectedRequests) = updateRequestStatuses()
updateJiraTickets(approvedRequests, rejectedRequests)
localCrlHandler?.signCrl()
}
private fun updateRequestStatuses(): Pair<List<ApprovedRequest>, List<RejectedRequest>> {
// Update local request statuses.
val approvedRequest = jiraClient.getApprovedRequests()
approvedRequest.forEachWithExceptionLogging(logger) { (id, approvedBy) -> crrStorage.approveRevocationRequest(id, approvedBy) }
val rejectedRequest = jiraClient.getRejectedRequests()
rejectedRequest.forEachWithExceptionLogging(logger) { (id, rejectedBy, reason) -> crrStorage.rejectRevocationRequest(id, rejectedBy, reason) }
return Pair(approvedRequest, rejectedRequest)
}
private fun updateJiraTickets(approvedRequest: List<ApprovedRequest>, rejectedRequest: List<RejectedRequest>) {
// Reconfirm request status and update jira status
logger.debug("Updating JIRA tickets: `approved` = $approvedRequest, `rejected` = $rejectedRequest")
approvedRequest.mapNotNull { crrStorage.getRevocationRequest(it.requestId) }
.filter { it.status == RequestStatus.DONE }
.forEachWithExceptionLogging(logger) { jiraClient.transitRequestStatusToDone(it.requestId) }
rejectedRequest.mapNotNull { crrStorage.getRevocationRequest(it.requestId) }
.filter { it.status == RequestStatus.REJECTED }
.forEachWithExceptionLogging(logger) { jiraClient.updateRejectedRequest(it.requestId) }
}
/**
* Creates Jira tickets for all request in [RequestStatus.NEW] state.
*
* Usually requests are expected to move to the [RequestStatus.TICKET_CREATED] state immediately,
* they might be left in the [RequestStatus.NEW] state if Jira is down.
*/
private fun createTickets() {
crrStorage.getRevocationRequests(RequestStatus.NEW).forEachWithExceptionLogging(logger) {
createTicket(it)
}
}
private fun createTicket(revocationRequest: CertificateRevocationRequestData) {
jiraClient.createCertificateRevocationRequestTicket(revocationRequest)
crrStorage.markRequestTicketCreated(revocationRequest.requestId)
}
}

View File

@ -1,95 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.doorman.*
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.bouncycastle.pkcs.PKCS10CertificationRequest
class JiraCsrHandler(private val jiraClient: CsrJiraClient, private val storage: CertificateSigningRequestStorage, private val delegate: CsrHandler) : CsrHandler by delegate {
private companion object {
val logger = contextLogger()
}
override fun saveRequest(rawRequest: PKCS10CertificationRequest): String {
val requestId = delegate.saveRequest(rawRequest)
// Make sure request has been accepted.
try {
if (delegate.getResponse(requestId) !is CertificateResponse.Unauthorised) {
jiraClient.createCertificateSigningRequestTicket(CertificationRequestData(requestId, rawRequest))
storage.markRequestTicketCreated(requestId)
}
} catch (e: Exception) {
logger.warn("There was an error while creating Jira tickets", e)
} finally {
return requestId
}
}
override fun processRequests() {
createTickets()
val (approvedRequests, rejectedRequests) = updateRequestStatus()
delegate.processRequests()
updateJiraTickets(approvedRequests, rejectedRequests)
}
private fun updateRequestStatus(): Pair<List<ApprovedRequest>, List<RejectedRequest>> {
// Update local request statuses.
val approvedRequest = jiraClient.getApprovedRequests()
approvedRequest.forEachWithExceptionLogging(logger) { (id, approvedBy) ->
storage.approveRequest(id, approvedBy)
}
val rejectedRequest = jiraClient.getRejectedRequests()
rejectedRequest.forEachWithExceptionLogging(logger) { (id, rejectedBy, reason) ->
storage.rejectRequest(id, rejectedBy, reason)
}
return Pair(approvedRequest, rejectedRequest)
}
private fun updateJiraTickets(approvedRequest: List<ApprovedRequest>, rejectedRequest: List<RejectedRequest>) {
// Reconfirm request status and update jira status
approvedRequest.mapNotNull { storage.getRequest(it.requestId) }
.filter { it.status == RequestStatus.DONE && it.certData != null }
.forEachWithExceptionLogging(logger) {
val attachment = it.certData?.certPath?.certificates?.firstOrNull()?.let {
Pair("${X509Utilities.CORDA_CLIENT_CA}.cer", it.encoded.inputStream())
}
jiraClient.transitRequestStatusToDone(it.requestId, attachment)
}
rejectedRequest.mapNotNull { storage.getRequest(it.requestId) }
.filter { it.status == RequestStatus.REJECTED }
.forEachWithExceptionLogging(logger) {
jiraClient.updateRejectedRequest(it.requestId)
}
}
/**
* Creates Jira tickets for all request in [RequestStatus.NEW] state.
*
* Usually requests are expected to move to the [RequestStatus.TICKET_CREATED] state immediately,
* they might be left in the [RequestStatus.NEW] state if Jira is down.
*/
private fun createTickets() {
storage.getRequests(RequestStatus.NEW).forEachWithExceptionLogging(logger) {
createTicket(it)
}
}
private fun createTicket(signingRequest: CertificateSigningRequest) {
jiraClient.createCertificateSigningRequestTicket(CertificationRequestData(signingRequest.requestId, signingRequest.request))
storage.markRequestTicketCreated(signingRequest.requestId)
}
}

View File

@ -1,30 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.signer.Signer
import net.corda.core.crypto.Crypto
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import java.security.KeyStore
import java.security.PrivateKey
import java.security.cert.X509Certificate
/**
* This local signer is intended to be used in testing environment where hardware signing module is not available.
*/
class LocalSigner(private val signingKey: PrivateKey, private val signingCert: X509Certificate) : Signer {
constructor(certAndKeyPair: CertificateAndKeyPair) : this(certAndKeyPair.keyPair.private, certAndKeyPair.certificate)
override fun signBytes(data: ByteArray): DigitalSignatureWithCert {
return DigitalSignatureWithCert(signingCert, Crypto.doSign(signingKey, data))
}
}

View File

@ -1,87 +0,0 @@
package com.r3.corda.networkmanage.doorman.sockets
import com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.sockets.*
import com.r3.corda.networkmanage.common.utils.readObject
import com.r3.corda.networkmanage.common.utils.writeObject
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.seconds
import java.net.ServerSocket
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class CertificateRevocationSocketServer(val port: Int,
private val crlStorage: CertificateRevocationListStorage,
private val crrStorage: CertificateRevocationRequestStorage) : AutoCloseable {
private companion object {
private val logger = contextLogger()
private val RECONNECT_INTERVAL = 10.seconds.toMillis()
}
private val executor = Executors.newSingleThreadScheduledExecutor()
@Volatile
private var isRunning = false
fun start() {
check(!isRunning) { "The server has already been started." }
isRunning = true
executor.submit {
while (isRunning) {
try {
listen()
} catch (e: Exception) {
logger.error("Socket execution error.", e)
if (isRunning) {
logger.info("Server socket will be recreated in $RECONNECT_INTERVAL milliseconds")
Thread.sleep(RECONNECT_INTERVAL)
logger.info("Recreating server socket...")
}
}
}
}
}
override fun close() {
isRunning = false
shutdownAndAwaitTermination(executor, RECONNECT_INTERVAL, TimeUnit.MILLISECONDS)
}
private fun listen() {
ServerSocket(port).use {
logger.info("Server socket is running.")
while (isRunning) {
logger.debug("Waiting for socket data...")
val acceptedSocket = it.accept()
acceptedSocket.use {
val message = it.getInputStream().readObject<CrrSocketMessage>()
logger.debug("Received message type is $message.")
acceptedSocket.getOutputStream().let {
when (message) {
is CrlRetrievalMessage -> {
val crl = crlStorage.getCertificateRevocationList(CrlIssuer.DOORMAN)
it.writeObject(CrlResponseMessage(crl))
logger.debug("Sending the current certificate revocation list.")
}
is CrrsByStatusMessage -> {
it.writeObject(crrStorage.getRevocationRequests(message.status))
logger.debug("Sending ${message.status.name} certificate revocation requests.")
}
is CrlSubmissionMessage -> {
val crlSubmission = acceptedSocket.getInputStream().readObject<CertificateRevocationListSubmission>()
crlStorage.saveCertificateRevocationList(crlSubmission.list, CrlIssuer.DOORMAN, crlSubmission.signer, crlSubmission.revocationTime)
}
else -> logger.warn("Unknown message type $message")
}
}
}
}
}
}
}

View File

@ -1,72 +0,0 @@
package com.r3.corda.networkmanage.doorman.webservice
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationListWebService.Companion.CRL_PATH
import net.corda.core.utilities.contextLogger
import java.time.Duration
import java.util.concurrent.TimeUnit
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok
import javax.ws.rs.core.Response.status
@Path(CRL_PATH)
class CertificateRevocationListWebService(private val revocationListStorage: CertificateRevocationListStorage,
private val caCrlBytes: ByteArray,
private val emptyCrlBytes: ByteArray,
cacheTimeout: Duration) {
companion object {
private val logger = contextLogger()
const val CRL_PATH = "certificate-revocation-list"
const val CRL_DATA_TYPE = "application/pkcs7-crl"
const val DOORMAN = "doorman"
const val ROOT = "root"
const val EMPTY = "empty"
}
private val crlCache: LoadingCache<CrlIssuer, ByteArray> = Caffeine.newBuilder()
.expireAfterWrite(cacheTimeout.toMillis(), TimeUnit.MILLISECONDS)
.build({ key ->
revocationListStorage.getCertificateRevocationList(key)?.encoded
})
@GET
@Path(DOORMAN)
@Produces(CRL_DATA_TYPE)
fun getDoormanRevocationList(): Response {
return getCrlResponse(CrlIssuer.DOORMAN)
}
@GET
@Path(ROOT)
@Produces(CRL_DATA_TYPE)
fun getRootRevocationList(): Response {
return ok(caCrlBytes).build()
}
@GET
@Path(EMPTY)
@Produces(CRL_DATA_TYPE)
fun getEmptyRevocationList(): Response {
return ok(emptyCrlBytes).build()
}
private fun getCrlResponse(issuer: CrlIssuer): Response {
return try {
val crlBytes = crlCache.get(issuer)
if (crlBytes != null) {
ok(crlBytes).build()
} else {
status(Response.Status.NOT_FOUND).build()
}
} catch (e: Exception) {
logger.error("Error when retrieving the ${issuer.name} crl.", e)
status(Response.Status.INTERNAL_SERVER_ERROR).build()
}
}
}

View File

@ -1,42 +0,0 @@
package com.r3.corda.networkmanage.doorman.webservice
import com.r3.corda.networkmanage.doorman.signer.CrrHandler
import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationRequestWebService.Companion.CRR_PATH
import net.corda.core.internal.readObject
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
import java.io.InputStream
import javax.ws.rs.Consumes
import javax.ws.rs.POST
import javax.ws.rs.Path
import javax.ws.rs.Produces
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
@Path(CRR_PATH)
class CertificateRevocationRequestWebService(private val crrHandler: CrrHandler) {
companion object {
const val CRR_PATH = "certificate-revocation-request"
val logger = contextLogger()
}
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.TEXT_PLAIN)
fun submitRequest(input: InputStream): Response {
return try {
val request = input.readObject<CertificateRevocationRequest>()
val requestId = crrHandler.saveRevocationRequest(request)
ok(requestId)
} catch (e: Exception) {
logger.warn("Unable to process the revocation request.", e)
when (e) {
is IllegalArgumentException -> status(Response.Status.BAD_REQUEST).entity(e.message)
else -> throw e
}
}.build()
}
}

View File

@ -1,28 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.webservice
import com.r3.corda.networkmanage.doorman.NetworkManagementServerStatus
import org.codehaus.jackson.map.ObjectMapper
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
@Path("/status")
class MonitoringWebService(private val serverStatus: NetworkManagementServerStatus) {
@GET
@Produces(MediaType.APPLICATION_JSON)
fun status(): Response {
return Response.ok(ObjectMapper().writeValueAsString(serverStatus)).build()
}
}

View File

@ -1,231 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.webservice
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.NetworkMaps
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity
import com.r3.corda.networkmanage.doorman.NetworkMapConfig
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService.Companion.NETWORK_MAP_PATH
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.internal.CertRole
import net.corda.core.internal.readObject
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.X509Utilities.validateCertPath
import net.corda.nodeapi.internal.crypto.x509
import net.corda.nodeapi.internal.crypto.x509Certificates
import java.io.InputStream
import java.security.InvalidKeyException
import java.security.SignatureException
import java.security.cert.CertPathValidatorException
import java.time.Duration
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.TimeUnit
import javax.servlet.http.HttpServletRequest
import javax.ws.rs.*
import javax.ws.rs.core.Context
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
@Path(NETWORK_MAP_PATH)
class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
private val networkMapStorage: NetworkMapStorage,
private val certificateSigningRequestStorage: CertificateSigningRequestStorage,
private val config: NetworkMapConfig) {
companion object {
val logger = contextLogger()
const val NETWORK_MAP_PATH = "network-map"
}
private val networkMapCache: LoadingCache<Boolean, NetworkMaps> = Caffeine.newBuilder()
.expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS)
.build {
logger.info("Re-publishing network map")
networkMapStorage.getNetworkMaps()
}
private val nodeInfoCache: LoadingCache<SecureHash, SignedNodeInfo> = Caffeine.newBuilder()
// TODO: Define cache retention policy.
.softValues()
.build(nodeInfoStorage::getNodeInfo)
private val networkMaps: NetworkMaps?
get() {
if (networkMapCache[true]?.publicNetworkMap == null) {
// Refresh cache every time the public network map is NULL.
networkMapCache.refresh(true)
}
return networkMapCache[true]
}
private val currentNodeInfoHashes: Set<SecureHash> get() = networkMaps?.allNodeInfoHashes ?: emptySet()
private val currentNetworkParameters: NetworkParameters? get() = networkMaps?.publicNetworkMap?.networkParameters?.networkParameters
@POST
@Path("publish")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
fun registerNode(input: InputStream): Response {
val signedNodeInfo = input.readObject<SignedNodeInfo>()
var nodeInfo: NodeInfo? = null
return try {
// Store the NodeInfo
val nodeInfoAndSigned = NodeInfoAndSigned(signedNodeInfo)
nodeInfo = nodeInfoAndSigned.nodeInfo
logger.debug { "Publishing node-info: $nodeInfo" }
verifyNodeInfo(nodeInfo)
nodeInfoStorage.putNodeInfo(nodeInfoAndSigned)
ok()
} catch (e: Exception) {
logger.warn("Unable to process node-info: $nodeInfo", e)
when (e) {
is NetworkMapNotInitialisedException -> status(Response.Status.SERVICE_UNAVAILABLE).entity(e.message)
is RequestException -> status(Response.Status.BAD_REQUEST).entity(e.message)
is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message)
// Rethrow e if its not one of the expected exception, the server will return http 500 internal error.
else -> throw e
}
}.build()
}
@POST
@Path("ack-parameters")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
fun ackNetworkParameters(input: InputStream): Response {
return try {
val signedParametersHash = input.readObject<SignedData<SecureHash>>()
val hash = signedParametersHash.verified()
requireNotNull(networkMapStorage.getSignedNetworkParameters(hash)) { "No network parameters with hash $hash" }
logger.debug { "Received ack-parameters with $hash from ${signedParametersHash.sig.by}" }
nodeInfoStorage.ackNodeInfoParametersUpdate(signedParametersHash.sig.by, hash)
ok()
} catch (e: SignatureException) {
status(Response.Status.FORBIDDEN).entity(e.message)
}.build()
}
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getNetworkMap(): Response = createNetworkMapResponse(networkMaps?.publicNetworkMap)
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getNetworkMap(@PathParam("id") privateNetworkID: String?): Response = createNetworkMapResponse(networkMaps?.privateNetworkMap?.get(privateNetworkID))
private fun createNetworkMapResponse(networkMap: NetworkMapEntity?) = createResponse(networkMap?.toSignedNetworkMap(), addCacheTimeout = true, timestamp = networkMap?.timestamp)
@GET
@Path("node-info/{nodeInfoHash}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getNodeInfo(@PathParam("nodeInfoHash") nodeInfoHash: String): Response {
// Only serve node info if its in the current network map, otherwise return 404.
logger.trace { "Processing node info request for hash: '$nodeInfoHash'" }
val signedNodeInfo = if (SecureHash.parse(nodeInfoHash) in currentNodeInfoHashes) {
nodeInfoCache.get(SecureHash.parse(nodeInfoHash))
} else {
logger.trace { "Requested node info is not current, returning null." }
null
}
logger.trace { "Node Info: ${signedNodeInfo?.verified()}" }
return createResponse(signedNodeInfo)
}
@GET
@Path("network-parameters/{hash}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getNetworkParameters(@PathParam("hash") hash: String): Response {
val signedNetParams = networkMapStorage.getSignedNetworkParameters(SecureHash.parse(hash))
logger.trace { "Precessed network parameter request for hash: '$hash'" }
logger.trace { "Network parameter : ${signedNetParams?.verified()}" }
return createResponse(signedNetParams)
}
@GET
@Path("my-ip")
fun myIp(@Context request: HttpServletRequest): Response {
val ip = request.getHeader("X-Forwarded-For")?.split(",")?.first() ?: "${request.remoteHost}:${request.remotePort}"
logger.trace { "Processed IP request from client, IP: '$ip'" }
return ok(ip).build()
}
private fun verifyNodeInfo(nodeInfo: NodeInfo) {
checkCertificates(nodeInfo)
checkCompositeKeys(nodeInfo)
val minimumPlatformVersion = currentNetworkParameters?.minimumPlatformVersion
?: throw NetworkMapNotInitialisedException("Network parameters have not been initialised")
if (nodeInfo.platformVersion < minimumPlatformVersion) {
throw RequestException("Minimum platform version is $minimumPlatformVersion")
}
}
private fun checkCompositeKeys(nodeInfo: NodeInfo) {
val compositeKeyIdentities = nodeInfo.legalIdentities.filter { it.owningKey is CompositeKey }
if (compositeKeyIdentities.isEmpty()) {
return
}
val parameters = checkNotNull(currentNetworkParameters) { "Network parameters not available." }
val notaryIdentities = parameters.notaries.map { it.identity }
if (!notaryIdentities.containsAll(compositeKeyIdentities)) {
throw RequestException("A composite key needs to belong to a notary.")
}
}
private fun createResponse(payload: Any?, addCacheTimeout: Boolean = false, timestamp: Instant? = null): Response {
return if (payload != null) {
val ok = Response.ok(payload.serialize().bytes)
if (addCacheTimeout) {
ok.header("Cache-Control", "max-age=${Duration.ofMillis(config.cacheTimeout).seconds}")
}
timestamp?.let {
ok.header("Last-Modified", DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneId.of("GMT")).format(it))
}
ok
} else {
status(Response.Status.NOT_FOUND)
}.build()
}
private fun checkCertificates(nodeInfo: NodeInfo) {
val nodeCaCert = nodeInfo.legalIdentitiesAndCerts.first().certPath.x509Certificates.find { CertRole.extract(it) == CertRole.NODE_CA }
nodeCaCert ?: throw RequestException("The node certificate path does not contain the node CA certificate type in it.")
val nodeCertPath = certificateSigningRequestStorage.getValidCertificatePath(nodeCaCert.publicKey)
nodeCertPath ?: throw RequestException("Node certificate is either no longer valid or was never registered.")
val rootCert = nodeCertPath.certificates.last().x509
try {
nodeInfo.legalIdentitiesAndCerts.forEach {
validateCertPath(rootCert, it.certPath)
}
} catch (e: CertPathValidatorException) {
throw RequestException("Invalid certificate path.")
}
}
class NetworkMapNotInitialisedException(message: String?) : Exception(message)
class RequestException(message: String) : Exception(message)
}

View File

@ -1,87 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.doorman.webservice
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.doorman.signer.CsrHandler
import net.corda.core.internal.readFully
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.isSignatureValid
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.time.Duration
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.servlet.http.HttpServletRequest
import javax.ws.rs.*
import javax.ws.rs.core.Context
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.*
import javax.ws.rs.core.Response.Status.UNAUTHORIZED
/**
* Provides functionality for asynchronous submission of certificate signing requests and retrieval of the results.
*/
@Path("certificate")
class RegistrationWebService(private val csrHandler: CsrHandler, private val clientPollInterval: Duration) {
@Context lateinit var request: HttpServletRequest
/**
* Accept stream of [PKCS10CertificationRequest] from user and persists in [CertificateRequestStorage] for approval.
* Server returns HTTP 200 response with random generated request Id after request has been persisted.
*/
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.TEXT_PLAIN)
fun submitRequest(input: InputStream): Response {
val csr = JcaPKCS10CertificationRequest(input.readFully())
if (!csr.isSignatureValid()) {
return status(Response.Status.BAD_REQUEST).entity("Invalid CSR signature").build()
}
val requestId = csrHandler.saveRequest(csr)
return ok(requestId).build()
}
/**
* Retrieve Certificate signing request from storage using the [requestId] and create a signed certificate if request has been approved.
* Returns HTTP 200 with DER encoded signed certificates if request has been approved else HTTP 204 No content
*/
@GET
@Path("{var}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
fun retrieveCert(@PathParam("var") requestId: String): Response {
val response = csrHandler.getResponse(requestId)
return when (response) {
is CertificateResponse.Ready -> {
// Write certificate chain to a zip stream and extract the bit array output.
val baos = ByteArrayOutputStream()
ZipOutputStream(baos).use { zip ->
// Client certificate must come first and root certificate should come last.
val certificates = ArrayList(response.certificatePath.certificates)
listOf(CORDA_CLIENT_CA, CORDA_INTERMEDIATE_CA, CORDA_ROOT_CA).zip(certificates).forEach {
zip.putNextEntry(ZipEntry("${it.first}.cer"))
zip.write(it.second.encoded)
zip.closeEntry()
}
}
ok(baos.toByteArray())
.type("application/zip")
.header("Content-Disposition", "attachment; filename=\"certificates.zip\"")
}
is CertificateResponse.NotReady -> noContent().header("Cache-Control", "max-age=${clientPollInterval.seconds}")
is CertificateResponse.Unauthorised -> status(UNAUTHORIZED).entity(response.message)
}.build()
}
}

View File

@ -1,67 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm
import com.jcabi.manifests.Manifests
import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.utils.initialiseSerialization
import com.r3.corda.networkmanage.common.utils.parseConfig
import com.r3.corda.networkmanage.hsm.configuration.ManualMode
import com.r3.corda.networkmanage.hsm.configuration.SigningServiceArgsParser
import com.r3.corda.networkmanage.hsm.configuration.SigningServiceConfig
import com.r3.corda.networkmanage.hsm.processor.CrrProcessor
import com.r3.corda.networkmanage.hsm.processor.CsrProcessor
import com.r3.corda.networkmanage.hsm.processor.NetworkMapProcessor
import net.corda.core.internal.exists
import org.apache.logging.log4j.LogManager
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.Security
import javax.crypto.Cipher
private val logger = LogManager.getLogger("com.r3.corda.networkmanage.hsm.Main")
fun main(args: Array<String>) {
if (Manifests.exists("Signing-Service-Version")) {
println("Signing Service Version: ${Manifests.read("Signing-Service-Version")}")
}
val cmdLineOptions = SigningServiceArgsParser().parseOrExit(*args)
val config = if (cmdLineOptions.configFile.exists()) {
parseConfig<SigningServiceConfig>(cmdLineOptions.configFile)
} else {
println("Please provide existing HSM config file using --config-file option")
return
}
// Validate
// Grabbed from https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available
require(Cipher.getMaxAllowedKeyLength("AES") >= 256) {
"Unlimited Strength Jurisdiction Policy Files must be installed, see http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html"
}
// Ensure the BouncyCastle provider is installed
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BouncyCastleProvider())
}
initialiseSerialization()
// Create DB connection.
val persistence = configureDatabase(config.dataSourceProperties, config.database)
if (config.networkMap != null) {
NetworkMapProcessor(config.networkMap, config.device, config.keySpecifier, persistence).run()
} else if (config.doorman != null) {
when (config.doorman.mode) {
ManualMode.CSR -> CsrProcessor(config.doorman, config.device, config.keySpecifier, persistence).showMenu()
ManualMode.CRL -> CrrProcessor(config.doorman, config.device, config.keySpecifier).showMenu()
}
}
}

View File

@ -1,18 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm.authentication
/**
* Supported authentication modes
*/
enum class AuthMode {
PASSWORD, CARD_READER, KEY_FILE
}

View File

@ -1,159 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm.authentication
import CryptoServerJCE.CryptoServerProvider
import com.r3.corda.networkmanage.common.signer.AuthenticationException
import java.io.ByteArrayOutputStream
import java.nio.file.Path
import kotlin.reflect.full.memberProperties
/**
* Performs user authentication against the HSM
*/
class Authenticator(private val mode: AuthMode = AuthMode.PASSWORD,
private val autoUsername: String? = null,
private val authKeyFilePath: Path? = null,
private val authKeyFilePass: String? = null,
private val authStrengthThreshold: Int = 2,
inputReader: InputReader = ConsoleInputReader(),
private val provider: CryptoServerProvider) : InputReader by inputReader {
/**
* Interactively (using console) authenticates a user against the HSM. Once authentication is successful the
* [block] is executed.
* @param block to be executed once the authentication process succeeds. The block should take 3 parameters:
* 1) [CryptoServerProvider] instance of the certificate provider
* 2) List of strings that corresponds to user names authenticated against the HSM.
*/
fun <T : Any> connectAndAuthenticate(block: (CryptoServerProvider, List<String>) -> T): T {
return try {
val authenticated = mutableListOf<String>()
loop@ while (true) {
val user = if (autoUsername.isNullOrEmpty()) {
print("Enter User Name (or Q to quit): ")
val input = readLine()
if (input != null && "q" == input.toLowerCase()) {
authenticated.clear()
break
}
input
} else {
println("Authenticating using preconfigured user name: $autoUsername")
autoUsername
}
when (mode) {
AuthMode.CARD_READER -> {
println("Authenticating using card reader")
println("Accessing the certificate key group data...")
provider.loginSign(user, ":cs2:cyb:USB0", null)
}
AuthMode.KEY_FILE -> {
println("Authenticating using preconfigured key file $authKeyFilePath")
val password = if (authKeyFilePass == null) {
val input = readPassword("Enter key file password (or Q to quit): ")
if ("q" == input.toLowerCase().trim()) {
authenticated.clear()
break@loop
} else {
input
}
} else {
authKeyFilePass
}
println("Accessing the certificate key group data...")
provider.loginSign(user, authKeyFilePath.toString(), password)
}
AuthMode.PASSWORD -> {
println("Authenticating using password")
val password = readPassword("Enter password (or Q to quit): ")
if ("q" == password.toLowerCase()) {
authenticated.clear()
break@loop
}
println("Accessing the certificate key group data...")
provider.loginPassword(user, password)
}
}
authenticated.add(user!!)
val auth = provider.cryptoServer.authState
if ((auth and 0x0000000F) >= authStrengthThreshold) {
println("Authentication sufficient")
break
} else {
println("Need more permissions. Add extra login")
}
}
if (!authenticated.isEmpty()) {
block(provider, authenticated)
} else {
throw AuthenticationException()
}
} finally {
try {
provider.logoff()
} catch (throwable: Throwable) {
println("WARNING Exception while logging off")
throwable.printStackTrace(System.out)
}
}
}
}
/*
* Configuration class for [CryptoServerProvider]
*/
data class CryptoServerProviderConfig(
val Device: String = "3001@127.0.0.1",
val ConnectionTimeout: Int = 30000,
val Timeout: Int = 60000,
val EndSessionOnShutdown: Int = 1,
val KeepSessionAlive: Int = 0,
val KeyGroup: String = "*",
val KeySpecifier: Int = -1,
val StoreKeysExternal: Boolean = false
)
/**
* Creates an instance of [CryptoServerProvider] that corresponds to the HSM.
*
* @param keyGroup HSM key group.
* @param keySpecifier HSM key specifier.
* @param device HSM device address.
*
* @return preconfigured instance of [CryptoServerProvider]
*/
fun createProvider(keyGroup: String, keySpecifier: Int, device: String): CryptoServerProvider {
val config = CryptoServerProviderConfig(
Device = device,
KeyGroup = keyGroup,
KeySpecifier = keySpecifier
)
return createProvider(config)
}
/**
* Creates an instance of [CryptoServerProvider] configured accordingly to the passed configuration.
*
* @param config crypto server provider configuration.
*
* @return preconfigured instance of [CryptoServerProvider]
*/
fun createProvider(config: CryptoServerProviderConfig): CryptoServerProvider {
val cfgBuffer = ByteArrayOutputStream()
val writer = cfgBuffer.writer(Charsets.UTF_8)
for (property in CryptoServerProviderConfig::class.memberProperties) {
writer.write("${property.name} = ${property.get(config)}\n")
}
writer.close()
val cfg = cfgBuffer.toByteArray().inputStream()
return CryptoServerProvider(cfg)
}

View File

@ -1,50 +0,0 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package com.r3.corda.networkmanage.hsm.authentication
/**
* User input reader interface
*/
interface InputReader {
/**
* Reads a single line from user's input.
*/
fun readLine(): String?
/**
* Reads a single line from user's input. The characters from the input are masked while being entered.
* @param format message string displayed before user's input
*/
fun readPassword(format: String): String
}
class ConsoleInputReader : InputReader {
private val console = System.console()
/** Read password from console, do a readLine instead if console is null (e.g. when debugging in IDE). */
override fun readPassword(format: String): String {
return if (console != null) {
String(console.readPassword(format))
} else {
print(format)
requireNotNull(kotlin.io.readLine()) { "Password required" }
}
}
/** Read console line */
override fun readLine(): String? {
return if (console == null) {
kotlin.io.readLine()
} else {
console.readLine()
}
}
}

Some files were not shown because too many files have changed in this diff Show More