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 84299dee0f..21c96e8cb1 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt @@ -83,7 +83,12 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CertificateAnd for (id in storage.getApprovedRequestIds()) { storage.approveRequest(id) { val request = JcaPKCS10CertificationRequest(request) - createCertificate(CertificateType.CLIENT_CA, caCertAndKey.certificate, caCertAndKey.keyPair, request.subject, request.publicKey, nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))), arrayOf())) + // The sub certs issued by the client must satisfy this directory name (or legal name in Corda) constraints, sub certs' directory name must be within client CA's name's subtree, + // 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) } logger.info("Approved request $id") serverStatus.lastApprovalTime = Instant.now() @@ -193,7 +198,7 @@ private fun DoormanParameters.startDoorman() { val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA).last() val caCertAndKey = keystore.getCertificateAndKeyPair(caPrivateKeyPassword, CORDA_INTERMEDIATE_CA) // Create DB connection. - val (datasource, database) = configureDatabase(dataSourceProperties) + val database = configureDatabase(dataSourceProperties).second val requestStorage = DBCertificateRequestStorage(database) diff --git a/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt b/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt index bce54f85fa..b6eb8bf9c1 100644 --- a/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt +++ b/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt @@ -10,6 +10,9 @@ import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import org.apache.commons.io.IOUtils 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 import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.junit.After @@ -29,7 +32,7 @@ class DoormanServiceTest { private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) private val rootCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Root CA,L=London"), rootCAKey) private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - private val intermediateCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey) + private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) private lateinit var doormanServer: DoormanServer private fun startSigningServer(storage: CertificationRequestStorage) { @@ -108,6 +111,48 @@ class DoormanServiceTest { } } + @Test + fun `retrieve certificate and create valid TLS certificate`() { + val keyPair = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) + val id = SecureHash.randomSHA256().toString() + + // Mock Storage behaviour. + val certificateStore = mutableMapOf() + val storage = mock { + on { getResponse(eq(id)) }.then { + certificateStore[id]?.let { CertificateResponse.Ready(it) } ?: CertificateResponse.NotReady + } + 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)) + certificateStore[id] = certGen(request) + Unit + } + on { getPendingRequestIds() }.then { listOf(id) } + } + + startSigningServer(storage) + + assertThat(pollForResponse(id)).isEqualTo(PollResponse.NotReady) + + storage.approveRequest(id) { + JcaPKCS10CertificationRequest(request).run { + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, X500Name("CN=LegalName, L=London")))), arrayOf()) + X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, subject, publicKey, nameConstraints = nameConstraints) + } + } + + val certificates = (pollForResponse(id) as PollResponse.Ready).certChain + verify(storage, times(2)).getResponse(any()) + assertEquals(3, certificates.size) + + val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val sslCert = X509Utilities.createCertificate(CertificateType.TLS, certificates.first(), keyPair, X500Name("CN=LegalName,L=London"), sslKey.public) + + X509Utilities.validateCertificateChain(certificates.last(), sslCert, *certificates.toTypedArray()) + } + @Test fun `request not authorised`() { val id = SecureHash.randomSHA256().toString() 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 d19f230a0b..eb72d58297 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 @@ -11,6 +11,9 @@ 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 import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.junit.After import org.junit.Before @@ -139,12 +142,8 @@ class DBCertificateRequestStorageTest { private fun approveRequest(requestId: String) { storage.approveRequest(requestId) { JcaPKCS10CertificationRequest(request).run { - X509Utilities.createCertificate( - CertificateType.TLS, - intermediateCACert, - intermediateCAKey, - subject, - publicKey) + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, subject))), arrayOf()) + X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, subject, publicKey, nameConstraints = nameConstraints) } } }