From 41dd1a71de9407cef1eae80b33ad81cc0dae665f Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Mon, 11 Sep 2017 11:32:04 +0100 Subject: [PATCH] Upgrade doorman to Corda 0.16-SNAPSHOT (#36) * * Upgrade to Corda 0.16 * validate X500 name using `validateX500Name` method * read email from CSR instead of X500Name * replaced Exposed with hibernate * * removed unused class * * make test helper method private * address PR issue --- doorman/build.gradle | 10 +- .../com/r3/corda/doorman/DoormanParameters.kt | 9 +- .../com/r3/corda/doorman/DoormanWebService.kt | 8 +- .../main/kotlin/com/r3/corda/doorman/Main.kt | 39 ++-- .../DBCertificateRequestStorage.kt | 192 +++++++++++------- .../persistence/DoormanSchemaService.kt | 21 ++ .../JiraCertificateRequestStorage.kt | 18 +- .../r3/corda/doorman/DoormanServiceTest.kt | 15 +- .../DBCertificateRequestStorageTest.kt | 47 +++-- 9 files changed, 231 insertions(+), 128 deletions(-) create mode 100644 doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DoormanSchemaService.kt diff --git a/doorman/build.gradle b/doorman/build.gradle index 0df6c3c717..0e0c72d525 100644 --- a/doorman/build.gradle +++ b/doorman/build.gradle @@ -1,7 +1,7 @@ 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' + corda_dependency_version = '0.16-SNAPSHOT' } version "$corda_dependency_version" @@ -40,10 +40,10 @@ task buildDoormanJAR(type: FatCapsule, dependsOn: 'jar') { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - 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" + compile "net.corda:corda-core:$corda_dependency_version" + compile "net.corda:corda-node:$corda_dependency_version" + compile "net.corda:corda-node-api:$corda_dependency_version" + testCompile "net.corda:corda-test-utils:$corda_dependency_version" // Log4J: logging framework (with SLF4J bindings) compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanParameters.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanParameters.kt index 09dba71225..e4c2540020 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanParameters.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanParameters.kt @@ -3,10 +3,10 @@ package com.r3.corda.doorman import com.r3.corda.doorman.OptionParserHelper.toConfigWithOptions import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions -import net.corda.core.div -import net.corda.node.utilities.getPath +import net.corda.core.internal.div import net.corda.nodeapi.config.parseAs import java.nio.file.Path +import java.nio.file.Paths import java.util.* data class DoormanParameters(val basedir: Path, @@ -17,6 +17,7 @@ data class DoormanParameters(val basedir: Path, val host: String, val port: Int, val dataSourceProperties: Properties, + val databaseProperties: Properties? = null, val keygen: Boolean = false, val rootKeygen: Boolean = false, val jiraConfig: JiraConfig? = null, @@ -55,9 +56,9 @@ fun parseParameters(vararg args: String): DoormanParameters { } val configFile = if (argConfig.hasPath("configFile")) { - argConfig.getPath("configFile") + Paths.get(argConfig.getString("configFile")) } else { - argConfig.getPath("basedir") / "node.conf" + Paths.get(argConfig.getString("basedir")) / "node.conf" } val config = argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))).resolve() return config.parseAs() diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanWebService.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanWebService.kt index 53f9cedf31..adc9a29764 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanWebService.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanWebService.kt @@ -3,10 +3,10 @@ package com.r3.corda.doorman import com.r3.corda.doorman.persistence.CertificateResponse import com.r3.corda.doorman.persistence.CertificationRequestData import com.r3.corda.doorman.persistence.CertificationRequestStorage -import net.corda.core.crypto.CertificateAndKeyPair -import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA -import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA -import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.core.utilities.CertificateAndKeyPair +import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_CA +import net.corda.node.utilities.X509Utilities.CORDA_INTERMEDIATE_CA +import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.codehaus.jackson.map.ObjectMapper import java.io.ByteArrayOutputStream diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt index a3bfd669a0..a85aff8664 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt @@ -5,22 +5,22 @@ import com.google.common.net.HostAndPort import com.r3.corda.doorman.DoormanServer.Companion.logger import com.r3.corda.doorman.persistence.CertificationRequestStorage import com.r3.corda.doorman.persistence.DBCertificateRequestStorage +import com.r3.corda.doorman.persistence.DoormanSchemaService import com.r3.corda.doorman.persistence.JiraCertificateRequestStorage -import net.corda.core.createDirectories -import net.corda.core.crypto.* -import net.corda.core.crypto.KeyStoreUtilities.loadKeyStore -import net.corda.core.crypto.KeyStoreUtilities.loadOrCreateKeyStore -import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA -import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA -import net.corda.core.crypto.X509Utilities.createCertificate -import net.corda.core.seconds +import net.corda.core.crypto.Crypto +import net.corda.core.internal.createDirectories +import net.corda.core.utilities.CertificateAndKeyPair import net.corda.core.utilities.loggerFor -import net.corda.node.utilities.configureDatabase +import net.corda.core.utilities.seconds +import net.corda.core.utilities.withCommonName +import net.corda.node.utilities.* +import net.corda.node.utilities.X509Utilities.CORDA_INTERMEDIATE_CA +import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA +import net.corda.node.utilities.X509Utilities.createCertificate 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 @@ -38,6 +38,7 @@ import java.time.Instant import kotlin.concurrent.thread import kotlin.system.exitProcess + /** * DoormanServer runs on Jetty server and provide certificate signing service via http. * The server will require keystorePath, keystore password and key password via command line input. @@ -88,8 +89,13 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CertificateAnd // 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 nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))), arrayOf()) - createCertificate(CertificateType.CLIENT_CA, caCertAndKey.certificate, caCertAndKey.keyPair, request.subject, request.publicKey, nameConstraints = nameConstraints).toX509Certificate() + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject.withCommonName(null)))), arrayOf()) + createCertificate(CertificateType.CLIENT_CA, + caCertAndKey.certificate, + caCertAndKey.keyPair, + request.subject.withCommonName(X509Utilities.CORDA_CLIENT_CA_CN), + request.publicKey, + nameConstraints = nameConstraints).toX509Certificate() } logger.info("Approved request $id") serverStatus.lastApprovalTime = Instant.now() @@ -151,7 +157,7 @@ private fun DoormanParameters.generateRootKeyPair() { val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) 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.addOrReplaceKey(CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert)) rootStore.save(rootStorePath, rootKeystorePassword) println("Root CA keypair and certificate stored in $rootStorePath.") @@ -183,7 +189,7 @@ private fun DoormanParameters.generateCAKeyPair() { val intermediateKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) 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(), CertPath(arrayOf(intermediateCert, rootKeyAndCert.certificate))) + caPrivateKeyPassword.toCharArray(), 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) @@ -199,7 +205,10 @@ private fun DoormanParameters.startDoorman() { val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA).last() val caCertAndKey = keystore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA, caPrivateKeyPassword) // Create DB connection. - val database = configureDatabase(dataSourceProperties).second + val database = configureDatabase(dataSourceProperties, databaseProperties, { DoormanSchemaService() }, createIdentityService = { + // Identity service not needed doorman, corda persistence is not very generic. + throw UnsupportedOperationException() + }) val requestStorage = DBCertificateRequestStorage(database) diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DBCertificateRequestStorage.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DBCertificateRequestStorage.kt index 8181a1eddf..e75b33f76d 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DBCertificateRequestStorage.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DBCertificateRequestStorage.kt @@ -2,85 +2,110 @@ 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.instant -import net.corda.node.utilities.transaction -import org.apache.commons.io.IOUtils +import net.corda.core.utilities.validateX500Name +import net.corda.core.utilities.withCommonName +import net.corda.node.utilities.CordaPersistence import org.bouncycastle.pkcs.PKCS10CertificationRequest -import org.jetbrains.exposed.sql.* import java.security.cert.Certificate import java.time.Instant -import javax.sql.rowset.serial.SerialBlob +import javax.persistence.* +import javax.persistence.criteria.CriteriaBuilder +import javax.persistence.criteria.Path +import javax.persistence.criteria.Predicate // TODO Relax the uniqueness requirement to be on the entire X.500 subject rather than just the legal name -class DBCertificateRequestStorage(private val database: Database) : CertificationRequestStorage { - private object DataTable : Table("certificate_signing_request") { - val requestId = varchar("request_id", 64).index().primaryKey() - val hostName = varchar("hostName", 100) - 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 requestTimestamp = instant("request_timestamp") - val processTimestamp = instant("process_timestamp").nullable() - val certificate = blob("certificate").nullable() - val rejectReason = varchar("reject_reason", 256).nullable() - } +class DBCertificateRequestStorage(private val database: CordaPersistence) : CertificationRequestStorage { + @Entity + @Table(name = "certificate_signing_request") + class CertificateSigningRequest( + @Id + @Column(name = "request_id", length = 64) + var requestId: String = "", - init { - // Create table if not exists. - database.transaction { - SchemaUtils.create(DataTable) - } - } + @Column(name = "host_name", length = 100) + var hostName: String = "", + + @Column(name = "ip_address", length = 15) + var ipAddress: String = "", + + @Column(name = "legal_name", length = 256) + var legalName: String = "", + + @Lob + @Column + var request: ByteArray = ByteArray(0), + + @Column(name = "request_timestamp") + var requestTimestamp: Instant = Instant.now(), + + @Column(name = "process_timestamp", nullable = true) + var processTimestamp: Instant? = null, + + @Lob + @Column(nullable = true) + var certificate: ByteArray? = null, + + @Column(name = "reject_reason", length = 256, nullable = true) + var rejectReason: String? = null + ) override fun saveRequest(certificationData: CertificationRequestData): String { - val legalName = certificationData.request.subject.commonName + val legalName = certificationData.request.subject.withCommonName(null) val requestId = SecureHash.randomSHA256().toString() database.transaction { - val duplicate = DataTable.select { - // A duplicate legal name is one where a previously approved, or currently pending, request has the same legal name. - // A rejected request with the same legal name doesn't count as a duplicate - DataTable.legalName eq legalName and (DataTable.certificate.isNotNull() or DataTable.processTimestamp.isNull()) - }.any() - val rejectReason = if (duplicate) { - "Duplicate legal name" - } else if ("[=,]".toRegex() in legalName) { - "Legal name cannot contain '=' or ','" - } else { - null - } - val now = Instant.now() - DataTable.insert { - it[this.requestId] = requestId - it[hostName] = certificationData.hostName - it[ipAddress] = certificationData.ipAddress - it[this.legalName] = legalName - it[request] = SerialBlob(certificationData.request.encoded) - it[requestTimestamp] = now - if (rejectReason != null) { - it[this.rejectReason] = rejectReason - it[processTimestamp] = now + val query = session.criteriaBuilder.run { + val criteriaQuery = createQuery(CertificateSigningRequest::class.java) + criteriaQuery.from(CertificateSigningRequest::class.java).run { + val nameEq = equal(get(CertificateSigningRequest::legalName.name), legalName.toString()) + val certNotNull = isNotNull(get(CertificateSigningRequest::certificate.name)) + val processTimeIsNull = isNull(get(CertificateSigningRequest::processTimestamp.name)) + criteriaQuery.where(and(nameEq, or(certNotNull, processTimeIsNull))) } } + val duplicate = session.createQuery(query).resultList.isNotEmpty() + val rejectReason = if (duplicate) { + "Duplicate legal name" + } else { + try { + validateX500Name(legalName) + null + } catch (e: IllegalArgumentException) { + "Name validation failed with exception : ${e.message}" + } + } + + val now = Instant.now() + val request = CertificateSigningRequest( + requestId, + certificationData.hostName, + certificationData.ipAddress, + legalName.toString(), + certificationData.request.encoded, + now, + rejectReason = rejectReason, + processTimestamp = rejectReason?.let { now } + ) + session.save(request) } return requestId } override fun getResponse(requestId: String): CertificateResponse { return database.transaction { - val response = DataTable - .select { DataTable.requestId eq requestId and DataTable.processTimestamp.isNotNull() } - .map { Pair(it[DataTable.certificate]?.let { IOUtils.toByteArray(it.binaryStream) }, it[DataTable.rejectReason]) } - .singleOrNull() + val response = singleRequestWhere { builder, path -> + val eq = builder.equal(path.get(CertificateSigningRequest::requestId.name), requestId) + val timeNotNull = builder.isNotNull(path.get(CertificateSigningRequest::processTimestamp.name)) + builder.and(eq, timeNotNull) + } + if (response == null) { CertificateResponse.NotReady } else { - val (certificate, rejectReason) = response + val certificate = response.certificate if (certificate != null) { CertificateResponse.Ready(CertificateUtilities.toX509Certificate(certificate)) } else { - CertificateResponse.Unauthorised(rejectReason!!) + CertificateResponse.Unauthorised(response.rejectReason!!) } } } @@ -88,46 +113,67 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio override fun approveRequest(requestId: String, generateCertificate: CertificationRequestData.() -> Certificate) { database.transaction { - val request = singleRequestWhere { DataTable.requestId eq requestId and DataTable.processTimestamp.isNull() } + val request = singleRequestWhere { builder, path -> + val eq = builder.equal(path.get(CertificateSigningRequest::requestId.name), requestId) + val timeIsNull = builder.isNull(path.get(CertificateSigningRequest::processTimestamp.name)) + builder.and(eq, timeIsNull) + } if (request != null) { - DataTable.update({ DataTable.requestId eq requestId }) { - it[certificate] = SerialBlob(request.generateCertificate().encoded) - it[processTimestamp] = Instant.now() - } + request.certificate = request.toRequestData().generateCertificate().encoded + request.processTimestamp = Instant.now() + session.save(request) } } } override fun rejectRequest(requestId: String, rejectReason: String) { database.transaction { - val request = singleRequestWhere { DataTable.requestId eq requestId and DataTable.processTimestamp.isNull() } + val request = singleRequestWhere { builder, path -> + val eq = builder.equal(path.get(CertificateSigningRequest::requestId.name), requestId) + val timeIsNull = builder.isNull(path.get(CertificateSigningRequest::processTimestamp.name)) + builder.and(eq, timeIsNull) + } if (request != null) { - DataTable.update({ DataTable.requestId eq requestId }) { - it[this.rejectReason] = rejectReason - it[processTimestamp] = Instant.now() - } + request.rejectReason = rejectReason + request.processTimestamp = Instant.now() + session.save(request) } } } override fun getRequest(requestId: String): CertificationRequestData? { return database.transaction { - singleRequestWhere { DataTable.requestId eq requestId } - } + singleRequestWhere { builder, path -> + builder.equal(path.get(CertificateSigningRequest::requestId.name), requestId) + } + }?.toRequestData() } override fun getPendingRequestIds(): List { return database.transaction { - DataTable.select { DataTable.processTimestamp.isNull() }.map { it[DataTable.requestId] } + val builder = session.criteriaBuilder + val query = builder.createQuery(String::class.java).run { + from(CertificateSigningRequest::class.java).run { + select(get(CertificateSigningRequest::requestId.name)) + where(builder.isNull(get(CertificateSigningRequest::processTimestamp.name))) + } + } + session.createQuery(query).resultList } } override fun getApprovedRequestIds(): List = emptyList() - private fun singleRequestWhere(where: SqlExpressionBuilder.() -> Op): CertificationRequestData? { - return DataTable - .select(where) - .map { CertificationRequestData(it[DataTable.hostName], it[DataTable.ipAddress], PKCS10CertificationRequest(IOUtils.toByteArray(it[DataTable.request].binaryStream))) } - .singleOrNull() + private fun singleRequestWhere(predicate: (CriteriaBuilder, Path) -> Predicate): CertificateSigningRequest? { + return database.transaction { + val builder = session.criteriaBuilder + val criteriaQuery = builder.createQuery(CertificateSigningRequest::class.java) + val query = criteriaQuery.from(CertificateSigningRequest::class.java).run { + criteriaQuery.where(predicate(builder, this)) + } + session.createQuery(query).uniqueResultOptional().orElse(null) + } } + + private fun CertificateSigningRequest.toRequestData() = CertificationRequestData(hostName, ipAddress, PKCS10CertificationRequest(request)) } \ No newline at end of file diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DoormanSchemaService.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DoormanSchemaService.kt new file mode 100644 index 0000000000..4e765bbd8f --- /dev/null +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DoormanSchemaService.kt @@ -0,0 +1,21 @@ +package com.r3.corda.doorman.persistence + +import net.corda.core.contracts.ContractState +import net.corda.core.schemas.MappedSchema +import net.corda.core.schemas.PersistentState +import net.corda.node.services.api.SchemaService + +class DoormanSchemaService : SchemaService { + // Entities for compulsory services + object DoormanServices + + object DoormanServicesV1 : MappedSchema(schemaFamily = DoormanServices.javaClass, version = 1, + mappedTypes = listOf(DBCertificateRequestStorage.CertificateSigningRequest::class.java)) + + override val schemaOptions: Map = mapOf(Pair(DoormanServicesV1, SchemaService.SchemaOptions())) + + override fun selectSchemas(state: ContractState): Iterable = setOf(DoormanServicesV1) + + override fun generateMappedObject(state: ContractState, schema: MappedSchema): PersistentState = throw UnsupportedOperationException() + +} \ No newline at end of file diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/JiraCertificateRequestStorage.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/JiraCertificateRequestStorage.kt index bca5678d43..b8257301c7 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/JiraCertificateRequestStorage.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/JiraCertificateRequestStorage.kt @@ -5,9 +5,11 @@ import com.atlassian.jira.rest.client.api.domain.Field import com.atlassian.jira.rest.client.api.domain.IssueType import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder import com.atlassian.jira.rest.client.api.domain.input.TransitionInput -import net.corda.core.crypto.X509Utilities -import net.corda.core.crypto.commonName +import net.corda.core.utilities.country +import net.corda.core.utilities.locality import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.organisation +import net.corda.node.utilities.X509Utilities import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemObject @@ -39,14 +41,16 @@ class JiraCertificateRequestStorage(val delegate: CertificationRequestStorage, JcaPEMWriter(request).use { it.writeObject(PemObject("CERTIFICATE REQUEST", certificationData.request.encoded)) } - val commonName = certificationData.request.subject.commonName - val email = certificationData.request.subject.getRDNs(BCStyle.EmailAddress).firstOrNull()?.first?.value - val nearestCity = certificationData.request.subject.getRDNs(BCStyle.L).firstOrNull()?.first?.value + val organisation = certificationData.request.subject.organisation + val nearestCity = certificationData.request.subject.locality + val country = certificationData.request.subject.country + + val email = certificationData.request.getAttributes(BCStyle.E).firstOrNull()?.attrValues?.firstOrNull()?.toString() val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id) .setProjectKey(projectCode) - .setDescription("Legal Name: $commonName\nNearest City: $nearestCity\nEmail: $email\n\n{code}$request{code}") - .setSummary(commonName) + .setDescription("Organisation: $organisation\nNearest City: $nearestCity\nCountry: $country\nEmail: $email\n\n{code}$request{code}") + .setSummary(organisation) .setFieldValue(requestIdField.id, requestId) // This will block until the issue is created. jiraClient.issueClient.createIssue(issue.build()).fail { logger.error("Exception when creating JIRA issue.", it) }.claim() diff --git a/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt b/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt index 28eefb0c83..37da1fc9e6 100644 --- a/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt +++ b/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt @@ -5,8 +5,13 @@ import com.nhaarman.mockito_kotlin.* import com.r3.corda.doorman.persistence.CertificateResponse import com.r3.corda.doorman.persistence.CertificationRequestData import com.r3.corda.doorman.persistence.CertificationRequestStorage -import net.corda.core.crypto.* -import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SecureHash +import net.corda.core.utilities.CertificateAndKeyPair +import net.corda.node.utilities.CertificateStream +import net.corda.node.utilities.CertificateType +import net.corda.node.utilities.X509Utilities +import net.corda.node.utilities.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.asn1.x500.X500Name @@ -57,7 +62,7 @@ class DoormanServiceTest { startSigningServer(storage) val keyPair = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) - val request = X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName"), keyPair) + val request = X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName"), "my@mail.com", keyPair) // Post request to signing server via http. assertEquals(id, submitRequest(request)) @@ -80,7 +85,7 @@ class DoormanServiceTest { on { approveRequest(eq(id), any()) }.then { @Suppress("UNCHECKED_CAST") val certGen = it.arguments[1] as ((CertificationRequestData) -> Certificate) - val request = CertificationRequestData("", "", X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName,L=London"), keyPair)) + val request = CertificationRequestData("", "", X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName,L=London"), "my@mail.com", keyPair)) certificateStore[id] = certGen(request) Unit } @@ -126,7 +131,7 @@ class DoormanServiceTest { on { approveRequest(eq(id), any()) }.then { @Suppress("UNCHECKED_CAST") val certGen = it.arguments[1] as ((CertificationRequestData) -> Certificate) - val request = CertificationRequestData("", "", X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName,L=London"), keyPair)) + val request = CertificationRequestData("", "", X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName,L=London"), "my@mail.com", keyPair)) certificateStore[id] = certGen(request) Unit } diff --git a/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/DBCertificateRequestStorageTest.kt b/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/DBCertificateRequestStorageTest.kt index 122bea3f91..6f4dbb09e8 100644 --- a/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/DBCertificateRequestStorageTest.kt +++ b/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/DBCertificateRequestStorageTest.kt @@ -3,15 +3,16 @@ 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.persistence.DoormanSchemaService import com.r3.corda.doorman.toX509Certificate -import net.corda.core.crypto.CertificateType import net.corda.core.crypto.Crypto -import net.corda.core.crypto.X509Utilities -import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME +import net.corda.core.crypto.SecureHash +import net.corda.core.utilities.getX500Name +import net.corda.node.utilities.CertificateType +import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.X509Utilities import net.corda.node.utilities.configureDatabase -import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat -import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints @@ -19,29 +20,27 @@ import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import java.security.KeyPair +import java.util.* import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue class DBCertificateRequestStorageTest { private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - private val intermediateCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Intermediate CA"), intermediateCAKey) - private var closeDb: Closeable? = null + private val intermediateCACert = X509Utilities.createSelfSignedCACertificate(getX500Name(CN = "Corda Node Intermediate CA", O = "R3 Ltd", L = "London", C = "GB"), intermediateCAKey) private lateinit var storage: DBCertificateRequestStorage + private lateinit var persistence: CordaPersistence @Before fun startDb() { - configureDatabase(makeTestDataSourceProperties()).apply { - closeDb = first - storage = DBCertificateRequestStorage(second) - } + persistence = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), { DoormanSchemaService() }, createIdentityService = { throw UnsupportedOperationException() }) + storage = DBCertificateRequestStorage(persistence) } @After fun closeDb() { - closeDb?.close() + persistence.close() } @Test @@ -132,11 +131,11 @@ class DBCertificateRequestStorageTest { } private fun createRequest(legalName: String): Pair { - val keyPair = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val request = CertificationRequestData( "hostname", "0.0.0.0", - X509Utilities.createCertificateSigningRequest(X500Name("CN=$legalName"), keyPair)) + X509Utilities.createCertificateSigningRequest(getX500Name(O = legalName, L = "London", C = "GB"), "my@mail.com", keyPair)) return Pair(request, keyPair) } @@ -148,4 +147,22 @@ class DBCertificateRequestStorageTest { } } } + + private fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties { + val props = Properties() + props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") + props.setProperty("dataSource.url", "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") + props.setProperty("dataSource.user", "sa") + props.setProperty("dataSource.password", "") + return props + } + + private fun makeTestDatabaseProperties(key: String? = null, value: String? = null): Properties { + val props = Properties() + props.setProperty("transactionIsolationLevel", "repeatableRead") //for other possible values see net.corda.node.utilities.CordaPeristence.parserTransactionIsolationLevel(String) + if (key != null) { + props.setProperty(key, value) + } + return props + } }