mirror of
https://github.com/corda/corda.git
synced 2025-02-27 19:46:38 +00:00
Introducing audit table for Doorman and Signing-Server (#69)
* Introducing audit table for Doorman and Signing-Server * Addressing review comments * Removing TODO * Adding comment on auto-signing path and DOORMAN_SIGNATURE usage * Fixing integration tests
This commit is contained in:
parent
8428f78821
commit
e6ce42281f
@ -90,6 +90,9 @@ dependencies {
|
||||
// TypeSafe Config: for simple and human friendly config files.
|
||||
compile "com.typesafe:config:1.3.0"
|
||||
|
||||
// Hibernate audit plugin
|
||||
compile "org.hibernate:hibernate-envers:5.2.11.Final"
|
||||
|
||||
// Unit testing helpers.
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
|
@ -3,6 +3,7 @@ package com.r3.corda.doorman
|
||||
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory
|
||||
import com.r3.corda.doorman.DoormanServer.Companion.logger
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestStorage
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
|
||||
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
|
||||
import com.r3.corda.doorman.persistence.DoormanSchemaService
|
||||
import com.r3.corda.doorman.persistence.PersistenceNodeInfoStorage
|
||||
@ -215,7 +216,7 @@ private fun buildLocalSigner(parameters: DoormanParameters): Signer? {
|
||||
private class ApproveAllCertificateRequestStorage(private val delegate: CertificationRequestStorage) : CertificationRequestStorage by delegate {
|
||||
override fun saveRequest(rawRequest: PKCS10CertificationRequest): String {
|
||||
val requestId = delegate.saveRequest(rawRequest)
|
||||
approveRequest(requestId)
|
||||
approveRequest(requestId, DOORMAN_SIGNATURE)
|
||||
return requestId
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.r3.corda.doorman.persistence
|
||||
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.hibernate.envers.Audited
|
||||
import java.security.cert.CertPath
|
||||
import java.time.Instant
|
||||
import javax.persistence.*
|
||||
@ -16,6 +17,8 @@ interface CertificationRequestStorage {
|
||||
/**
|
||||
* 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 certificationData certificate request data to be persisted.
|
||||
* @param createdBy authority (its identifier) creating this request.
|
||||
*/
|
||||
fun saveRequest(rawRequest: PKCS10CertificationRequest): String
|
||||
|
||||
@ -31,28 +34,32 @@ interface CertificationRequestStorage {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return True if the request has been approved and false otherwise.
|
||||
*/
|
||||
// TODO: Merge status changing methods.
|
||||
fun approveRequest(requestId: String, approvedBy: String = DOORMAN_SIGNATURE): Boolean
|
||||
fun approveRequest(requestId: String, approvedBy: String): Boolean
|
||||
|
||||
/**
|
||||
* Reject the given request using the given reason.
|
||||
* @param requestId id of the certificate signing request
|
||||
* @param rejectBy authority (its identifier) rejecting this request.
|
||||
* @param rejectReason brief description of the rejection reason
|
||||
*/
|
||||
fun rejectRequest(requestId: String, rejectedBy: String = DOORMAN_SIGNATURE, rejectReason: String)
|
||||
fun rejectRequest(requestId: String, rejectedBy: String, rejectReason: String)
|
||||
|
||||
/**
|
||||
* Store certificate path with [requestId], this will store the encoded [CertPath] and transit request statue to [RequestStatus.Signed].
|
||||
*
|
||||
* @param requestId id of the certificate signing request
|
||||
* @param signedBy authority (its identifier) signing this request.
|
||||
* @throws IllegalArgumentException if request is not found or not in Approved state.
|
||||
*/
|
||||
fun putCertificatePath(requestId: String, certificates: CertPath, signedBy: List<String> = listOf(DOORMAN_SIGNATURE))
|
||||
fun putCertificatePath(requestId: String, certificates: CertPath, signedBy: List<String>)
|
||||
}
|
||||
|
||||
@Entity
|
||||
@Table(name = "certificate_signing_request", indexes = arrayOf(Index(name = "IDX_PUB_KEY_HASH", columnList = "public_key_hash")))
|
||||
// TODO: Use Hibernate Envers to audit the table instead of individual "changed_by"/"changed_at" columns.
|
||||
class CertificateSigningRequest(
|
||||
@Id
|
||||
@Column(name = "request_id", length = 64)
|
||||
@ -66,34 +73,23 @@ class CertificateSigningRequest(
|
||||
@Column
|
||||
var request: ByteArray = ByteArray(0),
|
||||
|
||||
@Column(name = "created_at")
|
||||
var createdAt: Instant = Instant.now(),
|
||||
|
||||
@Column(name = "approved_at")
|
||||
var approvedAt: Instant = Instant.now(),
|
||||
|
||||
@Column(name = "approved_by", length = 64)
|
||||
var approvedBy: String? = null,
|
||||
|
||||
@Column
|
||||
@Audited
|
||||
@Column(name = "status")
|
||||
@Enumerated(EnumType.STRING)
|
||||
var status: RequestStatus = RequestStatus.New,
|
||||
|
||||
@Column(name = "signed_by", length = 512)
|
||||
@Audited
|
||||
@Column(name = "modified_by", length = 512)
|
||||
@ElementCollection(targetClass = String::class, fetch = FetchType.EAGER)
|
||||
var signedBy: List<String>? = null,
|
||||
var modifiedBy: List<String> = emptyList(),
|
||||
|
||||
@Column(name = "signed_at")
|
||||
var signedAt: Instant? = Instant.now(),
|
||||
@Audited
|
||||
@Column(name = "modified_at")
|
||||
var modifiedAt: Instant? = Instant.now(),
|
||||
|
||||
@Column(name = "rejected_by", length = 64)
|
||||
var rejectedBy: String? = null,
|
||||
|
||||
@Column(name = "rejected_at")
|
||||
var rejectedAt: Instant? = Instant.now(),
|
||||
|
||||
@Column(name = "reject_reason", length = 256, nullable = true)
|
||||
var rejectReason: String? = null,
|
||||
@Audited
|
||||
@Column(name = "remark", length = 256, nullable = true)
|
||||
var remark: String? = null,
|
||||
|
||||
// TODO: The certificate data can have its own table.
|
||||
@Embedded
|
||||
|
@ -30,8 +30,8 @@ class DBCertificateRequestStorage(private val database: CordaPersistence) : Cert
|
||||
val publicKeyHash = certificates.certificates.first().publicKey.hash()
|
||||
request!!.certificateData = CertificateData(publicKeyHash, certificates.encoded, CertificateStatus.VALID)
|
||||
request.status = Signed
|
||||
request.signedBy = signedBy
|
||||
request.signedAt = Instant.now()
|
||||
request.modifiedBy = signedBy
|
||||
request.modifiedAt = Instant.now()
|
||||
session.save(request)
|
||||
}
|
||||
}
|
||||
@ -66,7 +66,8 @@ class DBCertificateRequestStorage(private val database: CordaPersistence) : Cert
|
||||
requestId = requestId,
|
||||
legalName = legalName.toString(),
|
||||
request = request.encoded,
|
||||
rejectReason = rejectReason,
|
||||
remark = rejectReason,
|
||||
modifiedBy = emptyList(),
|
||||
status = if (rejectReason == null) New else Rejected
|
||||
))
|
||||
}
|
||||
@ -81,8 +82,8 @@ class DBCertificateRequestStorage(private val database: CordaPersistence) : Cert
|
||||
builder.equal(path.get<String>(CertificateSigningRequest::status.name), New))
|
||||
}
|
||||
if (request != null) {
|
||||
request.approvedAt = Instant.now()
|
||||
request.approvedBy = approvedBy
|
||||
request.modifiedAt = Instant.now()
|
||||
request.modifiedBy = listOf(approvedBy)
|
||||
request.status = Approved
|
||||
session.save(request)
|
||||
approved = true
|
||||
@ -97,10 +98,10 @@ class DBCertificateRequestStorage(private val database: CordaPersistence) : Cert
|
||||
builder.equal(path.get<String>(CertificateSigningRequest::requestId.name), requestId)
|
||||
}
|
||||
if (request != null) {
|
||||
request.rejectReason = rejectReason
|
||||
request.remark = rejectReason
|
||||
request.status = Rejected
|
||||
request.rejectedBy = rejectedBy
|
||||
request.rejectedAt = Instant.now()
|
||||
request.modifiedBy = listOf(rejectedBy)
|
||||
request.modifiedAt = Instant.now()
|
||||
session.save(request)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import com.r3.corda.doorman.JiraClient
|
||||
import com.r3.corda.doorman.buildCertPath
|
||||
import com.r3.corda.doorman.persistence.CertificateResponse
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestStorage
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
|
||||
import com.r3.corda.doorman.persistence.RequestStatus
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
|
||||
@ -22,7 +23,9 @@ class DefaultCsrHandler(private val storage: CertificationRequestStorage, privat
|
||||
private fun processRequest(requestId: String, request: PKCS10CertificationRequest) {
|
||||
if (signer != null) {
|
||||
val certs = signer.sign(request)
|
||||
storage.putCertificatePath(requestId, certs)
|
||||
// Since Doorman is deployed in the auto-signing mode (i.e. signer != null),
|
||||
// we use DOORMAN_SIGNATURE as the signer.
|
||||
storage.putCertificatePath(requestId, certs, listOf(DOORMAN_SIGNATURE))
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +37,7 @@ class DefaultCsrHandler(private val storage: CertificationRequestStorage, privat
|
||||
val response = storage.getRequest(requestId)
|
||||
return when (response?.status) {
|
||||
RequestStatus.New, RequestStatus.Approved, null -> CertificateResponse.NotReady
|
||||
RequestStatus.Rejected -> CertificateResponse.Unauthorised(response.rejectReason ?: "Unknown reason")
|
||||
RequestStatus.Rejected -> CertificateResponse.Unauthorised(response.remark ?: "Unknown reason")
|
||||
RequestStatus.Signed -> CertificateResponse.Ready(buildCertPath(response.certificateData?.certificatePath ?: throw IllegalArgumentException("Certificate should not be null.")))
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class DefaultRequestProcessorTest {
|
||||
val requestStorage: CertificationRequestStorage = mock {
|
||||
on { getRequest("New") }.thenReturn(CertificateSigningRequest(status = RequestStatus.New))
|
||||
on { getRequest("Signed") }.thenReturn(CertificateSigningRequest(status = RequestStatus.Signed, certificateData = CertificateData("", buildCertPath(cert.toX509Certificate()).encoded, CertificateStatus.VALID)))
|
||||
on { getRequest("Rejected") }.thenReturn(CertificateSigningRequest(status = RequestStatus.Rejected, rejectReason = "Random reason"))
|
||||
on { getRequest("Rejected") }.thenReturn(CertificateSigningRequest(status = RequestStatus.Rejected, remark = "Random reason"))
|
||||
}
|
||||
val signer: Signer = mock()
|
||||
val requestProcessor = DefaultCsrHandler(requestStorage, signer)
|
||||
|
@ -1,8 +1,9 @@
|
||||
package com.r3.corda.doorman.internal.persistence
|
||||
|
||||
import com.r3.corda.doorman.buildCertPath
|
||||
import com.r3.corda.doorman.persistence.DoormanSchemaService
|
||||
import com.r3.corda.doorman.persistence.CertificateSigningRequest
|
||||
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
|
||||
import com.r3.corda.doorman.persistence.DoormanSchemaService
|
||||
import com.r3.corda.doorman.persistence.RequestStatus
|
||||
import com.r3.corda.doorman.toX509Certificate
|
||||
import net.corda.core.crypto.Crypto
|
||||
@ -16,12 +17,14 @@ import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||
import org.hibernate.envers.AuditReaderFactory
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.KeyPair
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
|
||||
|
||||
class DBCertificateRequestStorageTest {
|
||||
private lateinit var storage: DBCertificateRequestStorage
|
||||
@ -58,7 +61,7 @@ class DBCertificateRequestStorageTest {
|
||||
// Certificate should be empty.
|
||||
assertNull(storage.getRequest(requestId)!!.certificateData)
|
||||
// Store certificate to DB.
|
||||
val result = storage.approveRequest(requestId)
|
||||
val result = storage.approveRequest(requestId, DOORMAN_SIGNATURE)
|
||||
// Check request request has been approved
|
||||
assertTrue(result)
|
||||
// Check request is not ready yet.
|
||||
@ -73,10 +76,10 @@ class DBCertificateRequestStorageTest {
|
||||
val (request, _) = createRequest("LegalName")
|
||||
// Add request to DB.
|
||||
val requestId = storage.saveRequest(request)
|
||||
storage.approveRequest(requestId)
|
||||
storage.approveRequest(requestId, DOORMAN_SIGNATURE)
|
||||
|
||||
// When subsequent approval is performed
|
||||
val result = storage.approveRequest(requestId)
|
||||
val result = storage.approveRequest(requestId, DOORMAN_SIGNATURE)
|
||||
// Then check request has not been approved
|
||||
assertFalse(result)
|
||||
}
|
||||
@ -91,20 +94,20 @@ class DBCertificateRequestStorageTest {
|
||||
// Certificate should be empty.
|
||||
assertNull(storage.getRequest(requestId)!!.certificateData)
|
||||
// Store certificate to DB.
|
||||
storage.approveRequest(requestId)
|
||||
storage.approveRequest(requestId, DOORMAN_SIGNATURE)
|
||||
// Check request is not ready yet.
|
||||
assertEquals(RequestStatus.Approved, storage.getRequest(requestId)!!.status)
|
||||
// New request should be empty.
|
||||
assertTrue(storage.getRequests(RequestStatus.New).isEmpty())
|
||||
// Sign certificate
|
||||
storage.putCertificatePath(requestId, JcaPKCS10CertificationRequest(csr).run {
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey)
|
||||
val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate()
|
||||
buildCertPath(ourCertificate, intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())
|
||||
})
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey)
|
||||
val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate()
|
||||
buildCertPath(ourCertificate, intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())
|
||||
}, listOf(DOORMAN_SIGNATURE))
|
||||
// Check request is ready
|
||||
assertNotNull(storage.getRequest(requestId)!!.certificateData)
|
||||
}
|
||||
@ -115,18 +118,18 @@ class DBCertificateRequestStorageTest {
|
||||
// Add request to DB.
|
||||
val requestId = storage.saveRequest(csr)
|
||||
// Store certificate to DB.
|
||||
storage.approveRequest(requestId)
|
||||
storage.approveRequest(requestId, DOORMAN_SIGNATURE)
|
||||
storage.putCertificatePath(requestId, JcaPKCS10CertificationRequest(csr).run {
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey)
|
||||
val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate()
|
||||
buildCertPath(ourCertificate, intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())
|
||||
})
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey)
|
||||
val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate()
|
||||
buildCertPath(ourCertificate, intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())
|
||||
}, listOf(DOORMAN_SIGNATURE))
|
||||
// Sign certificate
|
||||
// When subsequent signature requested
|
||||
assertFailsWith(IllegalArgumentException::class){
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
storage.putCertificatePath(requestId, JcaPKCS10CertificationRequest(csr).run {
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey)
|
||||
@ -134,16 +137,16 @@ class DBCertificateRequestStorageTest {
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate()
|
||||
buildCertPath(ourCertificate, intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())
|
||||
})
|
||||
}, listOf(DOORMAN_SIGNATURE))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reject request`() {
|
||||
val requestId = storage.saveRequest(createRequest("BankA").first)
|
||||
storage.rejectRequest(requestId, rejectReason = "Because I said so!")
|
||||
storage.rejectRequest(requestId, DOORMAN_SIGNATURE, "Because I said so!")
|
||||
assertThat(storage.getRequests(RequestStatus.New)).isEmpty()
|
||||
assertThat(storage.getRequest(requestId)!!.rejectReason).isEqualTo("Because I said so!")
|
||||
assertThat(storage.getRequest(requestId)!!.remark).isEqualTo("Because I said so!")
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -153,30 +156,51 @@ class DBCertificateRequestStorageTest {
|
||||
val requestId2 = storage.saveRequest(createRequest("BankA").first)
|
||||
assertThat(storage.getRequests(RequestStatus.New).map { it.requestId }).containsOnly(requestId1)
|
||||
assertEquals(RequestStatus.Rejected, storage.getRequest(requestId2)!!.status)
|
||||
assertThat(storage.getRequest(requestId2)!!.rejectReason).containsIgnoringCase("duplicate")
|
||||
assertThat(storage.getRequest(requestId2)!!.remark).containsIgnoringCase("duplicate")
|
||||
// Make sure the first request is processed properly
|
||||
storage.approveRequest(requestId1)
|
||||
storage.approveRequest(requestId1, DOORMAN_SIGNATURE)
|
||||
assertThat(storage.getRequest(requestId1)!!.status).isEqualTo(RequestStatus.Approved)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request with the same legal name as a previously approved request`() {
|
||||
val requestId1 = storage.saveRequest(createRequest("BankA").first)
|
||||
storage.approveRequest(requestId1)
|
||||
storage.approveRequest(requestId1, DOORMAN_SIGNATURE)
|
||||
val requestId2 = storage.saveRequest(createRequest("BankA").first)
|
||||
assertThat(storage.getRequest(requestId2)!!.rejectReason).containsIgnoringCase("duplicate")
|
||||
assertThat(storage.getRequest(requestId2)!!.remark).containsIgnoringCase("duplicate")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request with the same legal name as a previously rejected request`() {
|
||||
val requestId1 = storage.saveRequest(createRequest("BankA").first)
|
||||
storage.rejectRequest(requestId1, rejectReason = "Because I said so!")
|
||||
storage.rejectRequest(requestId1, DOORMAN_SIGNATURE, "Because I said so!")
|
||||
val requestId2 = storage.saveRequest(createRequest("BankA").first)
|
||||
assertThat(storage.getRequests(RequestStatus.New).map { it.requestId }).containsOnly(requestId2)
|
||||
storage.approveRequest(requestId2)
|
||||
storage.approveRequest(requestId2, DOORMAN_SIGNATURE)
|
||||
assertThat(storage.getRequest(requestId2)!!.status).isEqualTo(RequestStatus.Approved)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `audit data is available for CSRs`() {
|
||||
// given
|
||||
val approver = "APPROVER"
|
||||
|
||||
// when
|
||||
val requestId = storage.saveRequest(createRequest("BankA").first)
|
||||
storage.approveRequest(requestId, approver)
|
||||
|
||||
// then
|
||||
persistence.transaction {
|
||||
val auditReader = AuditReaderFactory.get(persistence.entityManagerFactory.createEntityManager())
|
||||
val newRevision = auditReader.find(CertificateSigningRequest::class.java, requestId, 1)
|
||||
assertEquals(RequestStatus.New, newRevision.status)
|
||||
assertTrue(newRevision.modifiedBy.isEmpty())
|
||||
val approvedRevision = auditReader.find(CertificateSigningRequest::class.java, requestId, 2)
|
||||
assertEquals(RequestStatus.Approved, approvedRevision.status)
|
||||
assertEquals(approver, approvedRevision.modifiedBy.first())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRequest(legalName: String): Pair<PKCS10CertificationRequest, KeyPair> {
|
||||
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val request = X509Utilities.createCertificateSigningRequest(CordaX500Name(organisation = legalName, locality = "London", country = "GB"), "my@mail.com", keyPair)
|
||||
|
@ -3,6 +3,7 @@ package com.r3.corda.doorman.internal.persistence
|
||||
import com.r3.corda.doorman.buildCertPath
|
||||
import com.r3.corda.doorman.hash
|
||||
import com.r3.corda.doorman.persistence.*
|
||||
import com.r3.corda.doorman.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
|
||||
import com.r3.corda.doorman.toX509Certificate
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.sha256
|
||||
@ -79,11 +80,11 @@ class PersistenceNodeInfoStorageTest {
|
||||
val request = X509Utilities.createCertificateSigningRequest(nodeInfo.legalIdentities.first().name, "my@mail.com", keyPair)
|
||||
|
||||
val requestId = requestStorage.saveRequest(request)
|
||||
requestStorage.approveRequest(requestId)
|
||||
requestStorage.approveRequest(requestId, DOORMAN_SIGNATURE)
|
||||
|
||||
assertNull(nodeInfoStorage.getCertificatePath(keyPair.public.hash()))
|
||||
|
||||
requestStorage.putCertificatePath(requestId, buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()))
|
||||
requestStorage.putCertificatePath(requestId, buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()), listOf(DOORMAN_SIGNATURE))
|
||||
|
||||
val storedCertPath = nodeInfoStorage.getCertificatePath(keyPair.public.hash())
|
||||
assertNotNull(storedCertPath)
|
||||
|
@ -81,6 +81,9 @@ dependencies {
|
||||
// TypeSafe Config: for simple and human friendly config files.
|
||||
compile "com.typesafe:config:1.3.0"
|
||||
|
||||
// Hibernate audit plugin
|
||||
compile "org.hibernate:hibernate-envers:5.2.11.Final"
|
||||
|
||||
// Unit testing helpers.
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
|
@ -8,6 +8,10 @@ import com.r3.corda.doorman.buildCertPath
|
||||
import com.r3.corda.doorman.persistence.DoormanSchemaService
|
||||
import com.r3.corda.doorman.startDoorman
|
||||
import com.r3.corda.doorman.toX509Certificate
|
||||
import com.r3.corda.signing.hsm.HsmSigner
|
||||
import com.r3.corda.signing.persistence.ApprovedCertificateRequestData
|
||||
import com.r3.corda.signing.persistence.DBCertificateRequestStorage
|
||||
import com.r3.corda.signing.persistence.SigningServerSchemaService
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
@ -17,10 +21,6 @@ import net.corda.node.utilities.X509Utilities
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
||||
import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
||||
import com.r3.corda.signing.hsm.HsmSigner
|
||||
import com.r3.corda.signing.persistence.ApprovedCertificateRequestData
|
||||
import com.r3.corda.signing.persistence.DBCertificateRequestStorage
|
||||
import com.r3.corda.signing.persistence.SigningServerSchemaService
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.CHARLIE
|
||||
@ -86,10 +86,10 @@ class SigningServiceIntegrationTest {
|
||||
@Test
|
||||
fun `Signing service communicates with Doorman`() {
|
||||
//Start doorman server
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), null, { DoormanSchemaService() }, createIdentityService = {
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), null, {
|
||||
// Identity service not needed doorman, corda persistence is not very generic.
|
||||
throw UnsupportedOperationException()
|
||||
})
|
||||
}, DoormanSchemaService())
|
||||
val doorman = startDoorman(NetworkHostAndPort(HOST, 0), database, approveAll = true)
|
||||
|
||||
// Start Corda network registration.
|
||||
@ -99,10 +99,10 @@ class SigningServiceIntegrationTest {
|
||||
whenever(it.certificateSigningService).thenReturn(URL("http://$HOST:${doorman.hostAndPort.port}"))
|
||||
}
|
||||
|
||||
val signingServiceStorage = DBCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties(), makeNotInitialisingTestDatabaseProperties(), { SigningServerSchemaService() }, createIdentityService = {
|
||||
val signingServiceStorage = DBCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties(), makeNotInitialisingTestDatabaseProperties(), {
|
||||
// Identity service not needed doorman, corda persistence is not very generic.
|
||||
throw UnsupportedOperationException()
|
||||
}))
|
||||
}, SigningServerSchemaService()))
|
||||
|
||||
val hsmSigner = givenSignerSigningAllRequests(signingServiceStorage)
|
||||
// Poll the database for approved requests
|
||||
@ -133,10 +133,10 @@ class SigningServiceIntegrationTest {
|
||||
@Ignore
|
||||
fun `DEMO - Create CSR and poll`() {
|
||||
//Start doorman server
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), null, { DoormanSchemaService() }, createIdentityService = {
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), null, {
|
||||
// Identity service not needed doorman, corda persistence is not very generic.
|
||||
throw UnsupportedOperationException()
|
||||
})
|
||||
}, SigningServerSchemaService())
|
||||
val doorman = startDoorman(NetworkHostAndPort(HOST, 0), database, approveAll = true)
|
||||
|
||||
thread(start = true, isDaemon = true) {
|
||||
@ -150,7 +150,7 @@ class SigningServiceIntegrationTest {
|
||||
|
||||
val config = testNodeConfiguration(
|
||||
baseDirectory = tempFolder.root.toPath(),
|
||||
myLegalName = when(it) {
|
||||
myLegalName = when (it) {
|
||||
1 -> ALICE.name
|
||||
2 -> BOB.name
|
||||
3 -> CHARLIE.name
|
||||
|
@ -2,6 +2,7 @@ package com.r3.corda.signing.persistence
|
||||
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.hibernate.envers.Audited
|
||||
import java.security.cert.CertPath
|
||||
import java.sql.Connection
|
||||
import java.time.Instant
|
||||
@ -34,16 +35,19 @@ class DBCertificateRequestStorage(private val database: CordaPersistence) : Cert
|
||||
@Column(nullable = true)
|
||||
var certificatePath: ByteArray? = null,
|
||||
|
||||
@Column(name = "signed_by", length = 512)
|
||||
@ElementCollection(targetClass = String::class, fetch = FetchType.EAGER)
|
||||
var signedBy: List<String>? = null,
|
||||
|
||||
@Column(name = "signed_at")
|
||||
var signedAt: Instant? = Instant.now(),
|
||||
|
||||
@Audited
|
||||
@Column(name = "status")
|
||||
@Enumerated(EnumType.STRING)
|
||||
var status: Status = Status.Approved
|
||||
var status: Status = Status.Approved,
|
||||
|
||||
@Audited
|
||||
@Column(name = "modified_by", length = 512)
|
||||
@ElementCollection(targetClass = String::class, fetch = FetchType.EAGER)
|
||||
var modifiedBy: List<String> = emptyList(),
|
||||
|
||||
@Audited
|
||||
@Column(name = "modified_at")
|
||||
var modifiedAt: Instant? = Instant.now()
|
||||
)
|
||||
|
||||
override fun getApprovedRequests(): List<ApprovedCertificateRequestData> {
|
||||
@ -63,8 +67,8 @@ class DBCertificateRequestStorage(private val database: CordaPersistence) : Cert
|
||||
val now = Instant.now()
|
||||
request.certificatePath = it.certPath?.encoded
|
||||
request.status = Status.Signed
|
||||
request.signedAt = now
|
||||
request.signedBy = signers
|
||||
request.modifiedAt = now
|
||||
request.modifiedBy = signers
|
||||
session.update(request)
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ class DBCertificateRequestStorageTest {
|
||||
val request = getRequestById(it.requestId)
|
||||
assertNotNull(request)
|
||||
assertEquals(Status.Signed, request?.status)
|
||||
assertEquals(signers.toString(), request?.signedBy.toString())
|
||||
assertEquals(signers.toString(), request?.modifiedBy.toString())
|
||||
assertNotNull(request?.certificatePath)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user