Adding public key constraint (#319)

* ENT-1356 Adding public key constraint

* Addressing review comments

* Removing SERIALIZABLE from transaction

* Adding stashed changes

* Removing SERIALIZABLE from node info storage

* Addressing review comments

* Addressing possible certificate inconsitency (design gap) + clearing whole database for new liquibase changeset

* Addressing review comments
This commit is contained in:
Michal Kit
2018-01-22 14:10:33 +00:00
committed by GitHub
parent cee975c1c1
commit e1098dee4b
16 changed files with 186 additions and 109 deletions

View File

@ -20,6 +20,7 @@ abstract class TestBase {
requestId: String = SecureHash.randomSHA256().toString(),
status: RequestStatus = RequestStatus.NEW,
legalName: String = "TestLegalName",
publicKeyHash: SecureHash = SecureHash.randomSHA256(),
remark: String = "Test remark",
request: PKCS10CertificationRequest = mock(),
certData: CertificateData = mock(),
@ -29,6 +30,7 @@ abstract class TestBase {
requestId = requestId,
status = status,
legalName = legalName,
publicKeyHash = publicKeyHash,
remark = remark,
certData = certData,
request = request,
@ -36,11 +38,9 @@ abstract class TestBase {
)
}
protected fun certificateData(publicKeyHash: String = SecureHash.randomSHA256().toString(),
certStatus: CertificateStatus = CertificateStatus.VALID,
protected fun certificateData(certStatus: CertificateStatus = CertificateStatus.VALID,
certPath: CertPath = mock()): CertificateData {
return CertificateData(
publicKeyHash = publicKeyHash,
certStatus = certStatus,
certPath = certPath
)

View File

@ -19,6 +19,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import java.security.KeyPair
import java.security.cert.CertPath
import java.util.*
import javax.security.auth.x500.X500Principal
import kotlin.test.*
@ -69,7 +70,7 @@ class PersistentCertificateRequestStorageTest : TestBase() {
@Test
fun `sign request`() {
val (csr, _) = createRequest("LegalName")
val (csr, nodeKeyPair) = createRequest("LegalName")
// Add request to DB.
val requestId = storage.saveRequest(csr)
// New request should equals to 1.
@ -86,11 +87,7 @@ class PersistentCertificateRequestStorageTest : TestBase() {
// Sign certificate
storage.putCertificatePath(
requestId,
JcaPKCS10CertificationRequest(csr).run {
// TODO We need a utility in InternalUtils for converting X500Name -> CordaX500Name
val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(CordaX500Name.build(X500Principal(subject.encoded)))
buildCertPath(nodeCa.certificate, intermediateCa.certificate, rootCa.certificate)
},
generateSignedCertPath(csr, nodeKeyPair),
listOf(DOORMAN_SIGNATURE)
)
// Check request is ready
@ -99,33 +96,51 @@ class PersistentCertificateRequestStorageTest : TestBase() {
@Test
fun `sign request ignores subsequent sign requests`() {
val (csr, _) = createRequest("LegalName")
val (csr, nodeKeyPair) = createRequest("LegalName")
// Add request to DB.
val requestId = storage.saveRequest(csr)
// Store certificate to DB.
storage.markRequestTicketCreated(requestId)
storage.approveRequest(requestId, DOORMAN_SIGNATURE)
// Sign certificate
storage.putCertificatePath(
requestId,
JcaPKCS10CertificationRequest(csr).run {
val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(CordaX500Name.build(X500Principal(subject.encoded)))
buildCertPath(nodeCa.certificate, intermediateCa.certificate, rootCa.certificate)
},
generateSignedCertPath(csr, nodeKeyPair),
listOf(DOORMAN_SIGNATURE)
)
// Sign certificate
// When subsequent signature requested
assertFailsWith(IllegalArgumentException::class) {
storage.putCertificatePath(
requestId,
JcaPKCS10CertificationRequest(csr).run {
val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(CordaX500Name.build(X500Principal(subject.encoded)))
buildCertPath(nodeCa.certificate, intermediateCa.certificate, rootCa.certificate)
},
generateSignedCertPath(csr, nodeKeyPair),
listOf(DOORMAN_SIGNATURE))
}
}
@Test
fun `sign request rejects requests with the same public key`() {
val (csr, nodeKeyPair) = createRequest("LegalName")
// Add request to DB.
val requestId = storage.saveRequest(csr)
// Store certificate to DB.
storage.markRequestTicketCreated(requestId)
storage.approveRequest(requestId, DOORMAN_SIGNATURE)
// Sign certificate
storage.putCertificatePath(
requestId,
generateSignedCertPath(csr, nodeKeyPair),
listOf(DOORMAN_SIGNATURE)
)
// Sign certificate
// When request with the same public key is requested
val (newCsr, _) = createRequest("NewLegalName", nodeKeyPair)
val duplicateRequestId = storage.saveRequest(newCsr)
assertThat(storage.getRequests(RequestStatus.NEW)).isEmpty()
val duplicateRequest = storage.getRequest(duplicateRequestId)
assertThat(duplicateRequest!!.status).isEqualTo(RequestStatus.REJECTED)
assertThat(duplicateRequest.remark).isEqualTo("Duplicate public key")
}
@Test
fun `reject request`() {
val requestId = storage.saveRequest(createRequest("BankA").first)
@ -159,18 +174,14 @@ class PersistentCertificateRequestStorageTest : TestBase() {
@Test
fun `request with the same legal name as a previously signed request`() {
val csr = createRequest("BankA").first
val (csr, nodeKeyPair) = createRequest("BankA")
val requestId = storage.saveRequest(csr)
storage.markRequestTicketCreated(requestId)
storage.approveRequest(requestId, DOORMAN_SIGNATURE)
// Sign certificate
storage.putCertificatePath(
requestId,
JcaPKCS10CertificationRequest(csr).run {
// TODO We need a utility in InternalUtils for converting X500Name -> CordaX500Name
val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(CordaX500Name.build(X500Principal(subject.encoded)))
buildCertPath(nodeCa.certificate, intermediateCa.certificate, rootCa.certificate)
},
generateSignedCertPath(csr, nodeKeyPair),
listOf(DOORMAN_SIGNATURE)
)
val rejectedRequestId = storage.saveRequest(createRequest("BankA").first)
@ -215,6 +226,14 @@ class PersistentCertificateRequestStorageTest : TestBase() {
}
}
private fun generateSignedCertPath(csr: PKCS10CertificationRequest, keyPair: KeyPair): CertPath {
return JcaPKCS10CertificationRequest(csr).run {
// TODO We need a utility in InternalUtils for converting X500Name -> CordaX500Name
val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(CordaX500Name.build(X500Principal(subject.encoded)), keyPair)
buildCertPath(nodeCa.certificate, intermediateCa.certificate, rootCa.certificate)
}
}
private fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties {
val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
@ -225,8 +244,7 @@ class PersistentCertificateRequestStorageTest : TestBase() {
}
}
internal fun createRequest(organisation: String): Pair<PKCS10CertificationRequest, KeyPair> {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
internal fun createRequest(organisation: String, keyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)): Pair<PKCS10CertificationRequest, KeyPair> {
val request = X509Utilities.createCertificateSigningRequest(X500Principal("O=$organisation,L=London,C=GB"), "my@mail.com", keyPair)
return Pair(request, keyPair)
}
}

View File

@ -5,13 +5,11 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.signWithCert
import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.TestNodeInfoBuilder
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.assertj.core.api.Assertions.assertThat
@ -50,7 +48,7 @@ class PersistentNetworkMapStorageTest : TestBase() {
fun `saveNetworkMap and saveNetworkParameters create current network map and parameters`() {
// given
// Create node info.
val signedNodeInfo = createValidSignedNodeInfo("Test")
val (signedNodeInfo) = createValidSignedNodeInfo("Test", requestStorage)
val nodeInfoHash = nodeInfoStorage.putNodeInfo(signedNodeInfo)
val networkParameters = testNetworkParameters(emptyList())
@ -117,8 +115,8 @@ class PersistentNetworkMapStorageTest : TestBase() {
fun `getValidNodeInfoHashes returns only valid and signed node info hashes`() {
// given
// Create node infos.
val signedNodeInfoA = createValidSignedNodeInfo("TestA")
val signedNodeInfoB = createValidSignedNodeInfo("TestB")
val (signedNodeInfoA) = createValidSignedNodeInfo("TestA", requestStorage)
val (signedNodeInfoB) = createValidSignedNodeInfo("TestB", requestStorage)
// Put signed node info data
val nodeInfoHashA = nodeInfoStorage.putNodeInfo(signedNodeInfoA)
@ -139,15 +137,4 @@ class PersistentNetworkMapStorageTest : TestBase() {
// then
assertThat(validNodeInfoHash).containsOnly(nodeInfoHashA, nodeInfoHashB)
}
private fun createValidSignedNodeInfo(organisation: String): NodeInfoWithSigned {
val nodeInfoBuilder = TestNodeInfoBuilder()
val requestId = requestStorage.saveRequest(createRequest(organisation).first)
requestStorage.markRequestTicketCreated(requestId)
requestStorage.approveRequest(requestId, "TestUser")
val (identity) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB"))
val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1))
requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList())
return NodeInfoWithSigned(nodeInfoBuilder.buildWithSigned().second)
}
}

View File

@ -9,7 +9,6 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
@ -23,6 +22,7 @@ import org.junit.Before
import org.junit.Test
import java.security.PrivateKey
import java.security.cert.X509Certificate
import javax.security.auth.x500.X500Principal
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
@ -83,8 +83,8 @@ class PersistentNodeInfoStorageTest : TestBase() {
@Test
fun `getNodeInfo returns persisted SignedNodeInfo using the hash of just the NodeInfo`() {
// given
val (nodeA) = createValidSignedNodeInfo("TestA")
val (nodeB) = createValidSignedNodeInfo("TestB")
val (nodeA) = createValidSignedNodeInfo("TestA", requestStorage)
val (nodeB) = createValidSignedNodeInfo("TestB", requestStorage)
// Put signed node info data
nodeInfoStorage.putNodeInfo(nodeA)
@ -102,7 +102,7 @@ class PersistentNodeInfoStorageTest : TestBase() {
@Test
fun `same public key with different node info`() {
// Create node info.
val (node1, key) = createValidSignedNodeInfo("Test", serial = 1)
val (node1, key) = createValidSignedNodeInfo("Test", requestStorage)
val nodeInfo2 = node1.nodeInfo.copy(serial = 2)
val node2 = NodeInfoWithSigned(nodeInfo2.signWith(listOf(key)))
@ -120,7 +120,7 @@ class PersistentNodeInfoStorageTest : TestBase() {
@Test
fun `putNodeInfo persists SignedNodeInfo with its signature`() {
// given
val (nodeInfoWithSigned) = createValidSignedNodeInfo("Test")
val (nodeInfoWithSigned) = createValidSignedNodeInfo("Test", requestStorage)
// when
val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfoWithSigned)
@ -129,16 +129,17 @@ class PersistentNodeInfoStorageTest : TestBase() {
val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash)
assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signedNodeInfo.signatures)
}
}
private fun createValidSignedNodeInfo(organisation: String, serial: Long = 1): Pair<NodeInfoWithSigned, PrivateKey> {
val nodeInfoBuilder = TestNodeInfoBuilder()
val requestId = requestStorage.saveRequest(createRequest(organisation).first)
requestStorage.markRequestTicketCreated(requestId)
requestStorage.approveRequest(requestId, "TestUser")
val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB"))
val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1))
requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList())
val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(serial)
return Pair(NodeInfoWithSigned(signedNodeInfo), key)
}
internal fun createValidSignedNodeInfo(organisation: String,
storage: CertificationRequestStorage): Pair<NodeInfoWithSigned, PrivateKey> {
val (csr, nodeKeyPair) = createRequest(organisation)
val requestId = storage.saveRequest(csr)
storage.markRequestTicketCreated(requestId)
storage.approveRequest(requestId, "TestUser")
val nodeInfoBuilder = TestNodeInfoBuilder()
val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name.build(X500Principal(csr.subject.encoded)), nodeKeyPair)
storage.putCertificatePath(requestId, identity.certPath, listOf("Test"))
val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(1)
return Pair(NodeInfoWithSigned(signedNodeInfo), key)
}

View File

@ -28,7 +28,7 @@ class DefaultCsrHandlerTest : TestBase() {
val requestStorage: CertificationRequestStorage = mock {
on { getRequest("New") }.thenReturn(certificateSigningRequest())
on { getRequest("Signed") }.thenReturn(certificateSigningRequest(status = RequestStatus.SIGNED, certData = certificateData("", CertificateStatus.VALID, buildCertPath(cert))))
on { getRequest("Signed") }.thenReturn(certificateSigningRequest(status = RequestStatus.SIGNED, certData = certificateData(CertificateStatus.VALID, buildCertPath(cert))))
on { getRequest("Rejected") }.thenReturn(certificateSigningRequest(status = RequestStatus.REJECTED, remark = "Random reason"))
}
val requestProcessor = DefaultCsrHandler(requestStorage, null)

View File

@ -1,6 +1,7 @@
package com.r3.corda.networkmanage.doorman.signer
import com.nhaarman.mockito_kotlin.*
import com.r3.corda.networkmanage.TestBase
import com.r3.corda.networkmanage.common.persistence.*
import com.r3.corda.networkmanage.doorman.ApprovedRequest
import com.r3.corda.networkmanage.doorman.JiraClient
@ -18,7 +19,7 @@ import org.mockito.junit.MockitoRule
import java.security.cert.CertPath
import kotlin.test.assertEquals
class JiraCsrHandlerTest {
class JiraCsrHandlerTest : TestBase() {
@Rule
@JvmField
val mockitoRule: MockitoRule = MockitoJUnit.rule()
@ -76,7 +77,11 @@ class JiraCsrHandlerTest {
@Test
fun `create tickets`() {
val csr = CertificateSigningRequest(requestId, "name", RequestStatus.NEW, pkcS10CertificationRequest, null, emptyList(), null)
val csr = certificateSigningRequest(
requestId = requestId,
legalName = "name",
status = RequestStatus.NEW,
request = pkcS10CertificationRequest)
whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(listOf(csr))
// Test
@ -90,8 +95,8 @@ class JiraCsrHandlerTest {
fun `sync tickets status`() {
val id1 = SecureHash.randomSHA256().toString()
val id2 = SecureHash.randomSHA256().toString()
val csr1 = CertificateSigningRequest(id1, "name1", RequestStatus.NEW, pkcS10CertificationRequest, null, emptyList(), null)
val csr2 = CertificateSigningRequest(id2, "name2", RequestStatus.NEW, pkcS10CertificationRequest, null, emptyList(), null)
val csr1 = CertificateSigningRequest(id1, "name1", SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, emptyList(), null)
val csr2 = CertificateSigningRequest(id2, "name2", SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, emptyList(), null)
val requests = mutableMapOf(id1 to csr1, id2 to csr2)
@ -134,7 +139,7 @@ class JiraCsrHandlerTest {
// Sign request 1
val certPath = mock<CertPath>()
val certData = CertificateData("", CertificateStatus.VALID, certPath)
val certData = CertificateData(CertificateStatus.VALID, certPath)
requests[id1] = requests[id1]!!.copy(status = RequestStatus.SIGNED, certData = certData)
// Process request again.