Doorman changes for corda M12 testnet (#5)

* Doorman changes for corda M12

* Address PR issues - remove sun.security from test

* Address PR issues - fixed whitespace, added comments on corda version in doorman
This commit is contained in:
Patrick Kuo 2017-06-21 16:11:48 +01:00 committed by GitHub
parent f05caeea3d
commit a8d7fe11ea
7 changed files with 73 additions and 56 deletions

View File

@ -1,3 +1,11 @@
ext {
// We use Corda release artifact dependencies instead of project dependencies to make sure each doorman releases are
// align with the corresponding Corda release.
corda_dependency_version = '0.12.1'
}
version "$corda_dependency_version"
apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'kotlin'
@ -14,35 +22,28 @@ repositories {
}
task buildDoormanJAR(type: FatCapsule, dependsOn: 'jar') {
group = 'build'
applicationClass 'com.r3.corda.doorman.MainKt'
archiveName 'doorman.jar'
capsuleManifest {
systemProperties['log4j.configuration'] = 'log4j2.xml'
applicationVersion = corda_dependency_version
systemProperties['visualvm.display.name'] = 'Doorman'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
}
sourceSets {
main {
resources {
srcDir "../config/dev"
}
}
test {
resources {
srcDir "../config/test"
}
}
// Make the resulting JAR file directly executable on UNIX by prepending a shell script to it.
// This lets you run the file like so: ./corda.jar
// Other than being slightly less typing, this has one big advantage: Ctrl-C works properly in the terminal.
reallyExecutable { trampolining() }
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile project(":core")
compile project(":node")
compile project(":node-api")
testCompile project(":test-utils")
compile "net.corda:core:$corda_dependency_version"
compile "net.corda:node:$corda_dependency_version"
compile "net.corda:node-api:$corda_dependency_version"
testCompile "net.corda:test-utils:$corda_dependency_version"
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
@ -70,7 +71,7 @@ dependencies {
testCompile "org.assertj:assertj-core:${assertj_version}"
testCompile "com.nhaarman:mockito-kotlin:0.6.1"
compile ('com.atlassian.jira:jira-rest-java-client-core:4.0.0'){
compile('com.atlassian.jira:jira-rest-java-client-core:4.0.0') {
// The jira client includes jersey-core 1.5 which breaks everything.
exclude module: 'jersey-core'
}

View File

@ -64,7 +64,7 @@ class DoormanWebService(val intermediateCACertAndKey: CertificateAndKeyPair, val
// Client certificate must come first and root certificate should come last.
val entries = listOf(
CORDA_CLIENT_CA to response.certificate,
CORDA_INTERMEDIATE_CA to intermediateCACertAndKey.certificate,
CORDA_INTERMEDIATE_CA to intermediateCACertAndKey.certificate.toX509Certificate(),
CORDA_ROOT_CA to rootCert
)
entries.forEach {

View File

@ -20,6 +20,7 @@ import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.path.CertPath
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
@ -88,7 +89,7 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CertificateAnd
// We assume all attributes in the subject name has been checked prior approval.
// TODO: add validation to subject name.
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))), arrayOf())
createCertificate(CertificateType.CLIENT_CA, caCertAndKey.certificate, caCertAndKey.keyPair, request.subject, request.publicKey, nameConstraints = nameConstraints)
createCertificate(CertificateType.CLIENT_CA, caCertAndKey.certificate, caCertAndKey.keyPair, request.subject, request.publicKey, nameConstraints = nameConstraints).toX509Certificate()
}
logger.info("Approved request $id")
serverStatus.lastApprovalTime = Instant.now()
@ -149,8 +150,8 @@ private fun DoormanParameters.generateRootKeyPair() {
}
val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name(CORDA_ROOT_CA), selfSignKey)
rootStore.addOrReplaceKey(CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert))
val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Root CA, O=R3, OU=Corda, L=London, C=GB"), selfSignKey)
rootStore.addOrReplaceKey(CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), CertPath(arrayOf(selfSignCert)))
rootStore.save(rootStorePath, rootKeystorePassword)
println("Root CA keypair and certificate stored in $rootStorePath.")
@ -164,7 +165,7 @@ private fun DoormanParameters.generateCAKeyPair() {
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password: ")
val rootKeyStore = loadKeyStore(rootStorePath, rootKeystorePassword)
val rootKeyAndCert = rootKeyStore.getCertificateAndKeyPair(rootPrivateKeyPassword, CORDA_ROOT_CA)
val rootKeyAndCert = rootKeyStore.getCertificateAndKeyPair(CORDA_ROOT_CA, rootPrivateKeyPassword)
val keystorePassword = keystorePassword ?: readPassword("Keystore Password: ")
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password: ")
@ -180,9 +181,9 @@ private fun DoormanParameters.generateCAKeyPair() {
}
val intermediateKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCert = createCertificate(CertificateType.INTERMEDIATE_CA, rootKeyAndCert.certificate, rootKeyAndCert.keyPair, X500Name(CORDA_INTERMEDIATE_CA), intermediateKey.public)
val intermediateCert = createCertificate(CertificateType.INTERMEDIATE_CA, rootKeyAndCert.certificate, rootKeyAndCert.keyPair, X500Name("CN=Corda Intermediate CA, O=R3, OU=Corda, L=London, C=GB"), intermediateKey.public)
keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA, intermediateKey.private,
caPrivateKeyPassword.toCharArray(), arrayOf(intermediateCert, rootKeyAndCert.certificate))
caPrivateKeyPassword.toCharArray(), CertPath(arrayOf(intermediateCert, rootKeyAndCert.certificate)))
keyStore.save(keystorePath, keystorePassword)
println("Intermediate CA keypair and certificate stored in $keystorePath.")
println(loadKeyStore(keystorePath, keystorePassword).getCertificate(CORDA_INTERMEDIATE_CA).publicKey)
@ -196,7 +197,7 @@ private fun DoormanParameters.startDoorman() {
val keystore = loadOrCreateKeyStore(keystorePath, keystorePassword)
val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA).last()
val caCertAndKey = keystore.getCertificateAndKeyPair(caPrivateKeyPassword, CORDA_INTERMEDIATE_CA)
val caCertAndKey = keystore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA, caPrivateKeyPassword)
// Create DB connection.
val database = configureDatabase(dataSourceProperties).second

View File

@ -4,6 +4,11 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import joptsimple.ArgumentAcceptingOptionSpec
import joptsimple.OptionParser
import org.bouncycastle.cert.X509CertificateHolder
import java.io.ByteArrayInputStream
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
/**
* Convert commandline arguments to [Config] object will allow us to use kotlin delegate with [ConfigHelper].
@ -27,3 +32,11 @@ object OptionParserHelper {
}
class ShowHelpException(val parser: OptionParser) : Exception()
object CertificateUtilities {
fun toX509Certificate(byteArray: ByteArray): X509Certificate {
return CertificateFactory.getInstance("X509").generateCertificate(ByteArrayInputStream(byteArray)) as X509Certificate
}
}
fun X509CertificateHolder.toX509Certificate(): Certificate = CertificateUtilities.toX509Certificate(encoded)

View File

@ -1,8 +1,11 @@
package com.r3.corda.doorman.persistence
import com.r3.corda.doorman.CertificateUtilities
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.commonName
import net.corda.node.utilities.*
import net.corda.node.utilities.instant
import net.corda.node.utilities.transaction
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.jetbrains.exposed.sql.*
import java.security.cert.Certificate
import java.time.Instant
@ -15,10 +18,10 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio
val ipAddress = varchar("ip_address", 15)
val legalName = varchar("legal_name", 256)
// TODO : Do we need to store this in column? or is it ok with blob.
val request = blob("request")
val request = binary("request", 256)
val requestTimestamp = instant("request_timestamp")
val processTimestamp = instant("process_timestamp").nullable()
val certificate = blob("certificate").nullable()
val certificate = binary("certificate", 1024).nullable()
val rejectReason = varchar("reject_reason", 256).nullable()
}
@ -46,18 +49,16 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio
null
}
val now = Instant.now()
withFinalizables { finalizables ->
DataTable.insert {
it[this.requestId] = requestId
it[hostName] = certificationData.hostName
it[ipAddress] = certificationData.ipAddress
it[this.legalName] = legalName
it[request] = serializeToBlob(certificationData.request, finalizables)
it[requestTimestamp] = now
if (rejectReason != null) {
it[this.rejectReason] = rejectReason
it[processTimestamp] = now
}
DataTable.insert {
it[this.requestId] = requestId
it[hostName] = certificationData.hostName
it[ipAddress] = certificationData.ipAddress
it[this.legalName] = legalName
it[request] = certificationData.request.encoded
it[requestTimestamp] = now
if (rejectReason != null) {
it[this.rejectReason] = rejectReason
it[processTimestamp] = now
}
}
}
@ -75,7 +76,7 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio
} else {
val (certificate, rejectReason) = response
if (certificate != null) {
CertificateResponse.Ready(deserializeFromBlob<Certificate>(certificate))
CertificateResponse.Ready(CertificateUtilities.toX509Certificate(certificate))
} else {
CertificateResponse.Unauthorised(rejectReason!!)
}
@ -87,11 +88,9 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio
database.transaction {
val request = singleRequestWhere { DataTable.requestId eq requestId and DataTable.processTimestamp.isNull() }
if (request != null) {
withFinalizables { finalizables ->
DataTable.update({ DataTable.requestId eq requestId }) {
it[certificate] = serializeToBlob(request.generateCertificate(), finalizables)
it[processTimestamp] = Instant.now()
}
DataTable.update({ DataTable.requestId eq requestId }) {
it[certificate] = request.generateCertificate().encoded
it[processTimestamp] = Instant.now()
}
}
}
@ -126,7 +125,7 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio
private fun singleRequestWhere(where: SqlExpressionBuilder.() -> Op<Boolean>): CertificationRequestData? {
return DataTable
.select(where)
.map { CertificationRequestData(it[DataTable.hostName], it[DataTable.ipAddress], deserializeFromBlob(it[DataTable.request])) }
.map { CertificationRequestData(it[DataTable.hostName], it[DataTable.ipAddress], PKCS10CertificationRequest(it[DataTable.request])) }
.singleOrNull()
}
}

View File

@ -13,6 +13,7 @@ import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After
@ -36,7 +37,7 @@ class DoormanServiceTest {
private lateinit var doormanServer: DoormanServer
private fun startSigningServer(storage: CertificationRequestStorage) {
doormanServer = DoormanServer(HostAndPort.fromParts("localhost", 0), CertificateAndKeyPair(intermediateCACert, intermediateCAKey), rootCACert, storage)
doormanServer = DoormanServer(HostAndPort.fromParts("localhost", 0), CertificateAndKeyPair(intermediateCACert, intermediateCAKey), rootCACert.toX509Certificate(), storage)
doormanServer.start()
}
@ -92,7 +93,7 @@ class DoormanServiceTest {
storage.approveRequest(id) {
JcaPKCS10CertificationRequest(request).run {
X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey)
X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate()
}
}
@ -139,7 +140,7 @@ class DoormanServiceTest {
storage.approveRequest(id) {
JcaPKCS10CertificationRequest(request).run {
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, X500Name("CN=LegalName, L=London")))), arrayOf())
X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, subject, publicKey, nameConstraints = nameConstraints)
X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, subject, publicKey, nameConstraints = nameConstraints).toX509Certificate()
}
}
@ -148,9 +149,10 @@ class DoormanServiceTest {
assertEquals(3, certificates.size)
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val sslCert = X509Utilities.createCertificate(CertificateType.TLS, certificates.first(), keyPair, X500Name("CN=LegalName,L=London"), sslKey.public)
val sslCert = X509Utilities.createCertificate(CertificateType.TLS, X509CertificateHolder(certificates.first().encoded), keyPair, X500Name("CN=LegalName,L=London"), sslKey.public).toX509Certificate()
X509Utilities.validateCertificateChain(certificates.last(), sslCert, *certificates.toTypedArray())
// TODO: This is temporary solution, remove all certificate re-shaping after identity refactoring is done.
X509Utilities.validateCertificateChain(X509CertificateHolder(certificates.last().encoded), sslCert, *certificates.toTypedArray())
}
@Test

View File

@ -3,6 +3,7 @@ package com.r3.corda.doorman.internal.persistence
import com.r3.corda.doorman.persistence.CertificateResponse
import com.r3.corda.doorman.persistence.CertificationRequestData
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
import com.r3.corda.doorman.toX509Certificate
import net.corda.core.crypto.CertificateType
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.X509Utilities
@ -143,7 +144,7 @@ class DBCertificateRequestStorageTest {
storage.approveRequest(requestId) {
JcaPKCS10CertificationRequest(request).run {
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, subject))), arrayOf())
X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, subject, publicKey, nameConstraints = nameConstraints)
X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, subject, publicKey, nameConstraints = nameConstraints).toX509Certificate()
}
}
}