diff --git a/doorman/build.gradle b/doorman/build.gradle index 98207d99a2..a23d28b2f1 100644 --- a/doorman/build.gradle +++ b/doorman/build.gradle @@ -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}" 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 6ce55eb939..9fdfe9f76b 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt @@ -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 } } diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/CertificationRequestStorage.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/CertificationRequestStorage.kt index f219717e97..e27bbbe7ee 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/CertificationRequestStorage.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/CertificationRequestStorage.kt @@ -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 = listOf(DOORMAN_SIGNATURE)) + fun putCertificatePath(requestId: String, certificates: CertPath, signedBy: List) } @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? = null, + var modifiedBy: List = 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 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 798bd8b33f..63c4550423 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 @@ -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(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(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) } } diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/signer/CsrHandler.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/signer/CsrHandler.kt index 996effda55..bd2a9bce05 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/signer/CsrHandler.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/signer/CsrHandler.kt @@ -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."))) } } diff --git a/doorman/src/test/kotlin/com/r3/corda/doorman/DefaultRequestProcessorTest.kt b/doorman/src/test/kotlin/com/r3/corda/doorman/DefaultRequestProcessorTest.kt index e5c9a02a0f..124a4b9c3c 100644 --- a/doorman/src/test/kotlin/com/r3/corda/doorman/DefaultRequestProcessorTest.kt +++ b/doorman/src/test/kotlin/com/r3/corda/doorman/DefaultRequestProcessorTest.kt @@ -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) 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 53c42740c5..ba8f23bd74 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 @@ -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 { val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val request = X509Utilities.createCertificateSigningRequest(CordaX500Name(organisation = legalName, locality = "London", country = "GB"), "my@mail.com", keyPair) diff --git a/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/PersistenceNodeInfoStorageTest.kt b/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/PersistenceNodeInfoStorageTest.kt index 5772949ef8..87ebe5e189 100644 --- a/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/PersistenceNodeInfoStorageTest.kt +++ b/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/PersistenceNodeInfoStorageTest.kt @@ -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) diff --git a/signing-server/build.gradle b/signing-server/build.gradle index 1b893a5057..ccf218dc3a 100644 --- a/signing-server/build.gradle +++ b/signing-server/build.gradle @@ -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}" diff --git a/signing-server/src/integration-test/kotlin/com/r3/corda/signing/SigningServiceIntegrationTest.kt b/signing-server/src/integration-test/kotlin/com/r3/corda/signing/SigningServiceIntegrationTest.kt index 210493e5ae..e9e119223e 100644 --- a/signing-server/src/integration-test/kotlin/com/r3/corda/signing/SigningServiceIntegrationTest.kt +++ b/signing-server/src/integration-test/kotlin/com/r3/corda/signing/SigningServiceIntegrationTest.kt @@ -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 diff --git a/signing-server/src/main/kotlin/com/r3/corda/signing/persistence/DBCertificateRequestStorage.kt b/signing-server/src/main/kotlin/com/r3/corda/signing/persistence/DBCertificateRequestStorage.kt index b329f35b82..f8ccbfc5a3 100644 --- a/signing-server/src/main/kotlin/com/r3/corda/signing/persistence/DBCertificateRequestStorage.kt +++ b/signing-server/src/main/kotlin/com/r3/corda/signing/persistence/DBCertificateRequestStorage.kt @@ -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? = 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 = emptyList(), + + @Audited + @Column(name = "modified_at") + var modifiedAt: Instant? = Instant.now() ) override fun getApprovedRequests(): List { @@ -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) } } diff --git a/signing-server/src/test/kotlin/com/r3/corda/signing/persistence/DBCertificateRequestStorageTest.kt b/signing-server/src/test/kotlin/com/r3/corda/signing/persistence/DBCertificateRequestStorageTest.kt index 29eeea3c85..404a97286e 100644 --- a/signing-server/src/test/kotlin/com/r3/corda/signing/persistence/DBCertificateRequestStorageTest.kt +++ b/signing-server/src/test/kotlin/com/r3/corda/signing/persistence/DBCertificateRequestStorageTest.kt @@ -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) } }