mirror of
https://github.com/corda/corda.git
synced 2025-02-04 18:22:29 +00:00
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
This commit is contained in:
parent
a75dd409aa
commit
41dd1a71de
@ -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}"
|
||||
|
@ -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<DoormanParameters>()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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<String>(CertificateSigningRequest::legalName.name), legalName.toString())
|
||||
val certNotNull = isNotNull(get<String>(CertificateSigningRequest::certificate.name))
|
||||
val processTimeIsNull = isNull(get<String>(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<String>(CertificateSigningRequest::requestId.name), requestId)
|
||||
val timeNotNull = builder.isNotNull(path.get<Instant>(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<String>(CertificateSigningRequest::requestId.name), requestId)
|
||||
val timeIsNull = builder.isNull(path.get<Instant>(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<String>(CertificateSigningRequest::requestId.name), requestId)
|
||||
val timeIsNull = builder.isNull(path.get<Instant>(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<String>(CertificateSigningRequest::requestId.name), requestId)
|
||||
}
|
||||
}?.toRequestData()
|
||||
}
|
||||
|
||||
override fun getPendingRequestIds(): List<String> {
|
||||
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<String>(CertificateSigningRequest::processTimestamp.name)))
|
||||
}
|
||||
}
|
||||
session.createQuery(query).resultList
|
||||
}
|
||||
}
|
||||
|
||||
override fun getApprovedRequestIds(): List<String> = emptyList()
|
||||
|
||||
private fun singleRequestWhere(where: SqlExpressionBuilder.() -> Op<Boolean>): 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<CertificateSigningRequest>) -> 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))
|
||||
}
|
@ -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<MappedSchema, SchemaService.SchemaOptions> = mapOf(Pair(DoormanServicesV1, SchemaService.SchemaOptions()))
|
||||
|
||||
override fun selectSchemas(state: ContractState): Iterable<MappedSchema> = setOf(DoormanServicesV1)
|
||||
|
||||
override fun generateMappedObject(state: ContractState, schema: MappedSchema): PersistentState = throw UnsupportedOperationException()
|
||||
|
||||
}
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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<CertificationRequestData, KeyPair> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user