CORDA-1363 Network registration helper should check public key in certificate before storing in keystore (#3071)

* check pub key in network registration helper before storing in DB
This commit is contained in:
Patrick Kuo 2018-05-09 12:56:10 +01:00 committed by GitHub
parent a70e479696
commit be11da76c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 37 deletions

View File

@ -18,6 +18,7 @@ import java.io.StringWriter
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore import java.security.KeyStore
import java.security.PublicKey
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
/** /**
@ -85,14 +86,17 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration,
requestIdStore.deleteIfExists() requestIdStore.deleteIfExists()
throw certificateRequestException throw certificateRequestException
} }
validateCertificates(certificates) validateCertificates(keyPair.public, certificates)
storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias) storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias)
onSuccess(keyPair, certificates) onSuccess(keyPair, certificates)
// All done, clean up temp files. // All done, clean up temp files.
requestIdStore.deleteIfExists() requestIdStore.deleteIfExists()
println("Successfully registered Corda node with compatibility zone, node identity keys and certificates are stored in '${config.certificatesDirectory}', it is advised to backup the private keys and certificates.")
println("Corda node will now terminate.")
} }
private fun validateCertificates(certificates: List<X509Certificate>) { private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List<X509Certificate>) {
val nodeCACertificate = certificates.first() val nodeCACertificate = certificates.first()
val nodeCaSubject = try { val nodeCaSubject = try {
@ -114,6 +118,11 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration,
throw CertificateRequestException("Received certificate contains invalid cert role, expected '$certRole', got '$nodeCaCertRole'.") throw CertificateRequestException("Received certificate contains invalid cert role, expected '$certRole', got '$nodeCaCertRole'.")
} }
// Validate returned certificate is for the correct public key.
if (Crypto.toSupportedPublicKey(certificates.first().publicKey) != Crypto.toSupportedPublicKey(registeringPublicKey)) {
throw CertificateRequestException("Received certificate contains incorrect public key, expected '$registeringPublicKey', got '${certificates.first().publicKey}'.")
}
// Validate certificate chain returned from the doorman with the root cert obtained via out-of-band process, to prevent MITM attack on doorman server. // Validate certificate chain returned from the doorman with the root cert obtained via out-of-band process, to prevent MITM attack on doorman server.
X509Utilities.validateCertificateChain(rootCert, certificates) X509Utilities.validateCertificateChain(rootCert, certificates)
println("Certificate signing request approved, storing private key with the certificate chain.") println("Certificate signing request approved, storing private key with the certificate chain.")

View File

@ -3,8 +3,8 @@ package net.corda.node.utilities.registration
import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Configuration.unix
import com.google.common.jimfs.Jimfs import com.google.common.jimfs.Jimfs
import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doAnswer
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -17,6 +17,7 @@ import net.corda.core.utilities.seconds
import net.corda.node.NodeRegistrationOption import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
@ -27,9 +28,12 @@ import org.assertj.core.api.Assertions.*
import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.security.PublicKey
import java.security.cert.CertPathValidatorException import java.security.cert.CertPathValidatorException
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
@ -37,7 +41,6 @@ import kotlin.test.assertFalse
class NetworkRegistrationHelperTest { class NetworkRegistrationHelperTest {
private val fs = Jimfs.newFileSystem(unix()) private val fs = Jimfs.newFileSystem(unix())
private val requestId = SecureHash.randomSHA256().toString()
private val nodeLegalName = ALICE_NAME private val nodeLegalName = ALICE_NAME
private lateinit var config: NodeConfiguration private lateinit var config: NodeConfiguration
@ -69,10 +72,9 @@ class NetworkRegistrationHelperTest {
assertThat(config.sslKeystore).doesNotExist() assertThat(config.sslKeystore).doesNotExist()
assertThat(config.trustStoreFile).doesNotExist() assertThat(config.trustStoreFile).doesNotExist()
val nodeCaCertPath = createNodeCaCertPath() val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(it.first.certificate) }
saveNetworkTrustStore(nodeCaCertPath.last()) createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).buildKeystore()
createRegistrationHelper(nodeCaCertPath).buildKeystore()
val nodeKeystore = config.loadNodeKeyStore() val nodeKeystore = config.loadNodeKeyStore()
val sslKeystore = config.loadSslKeyStore() val sslKeystore = config.loadSslKeyStore()
@ -82,7 +84,7 @@ class NetworkRegistrationHelperTest {
assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(contains(X509Utilities.CORDA_ROOT_CA)) assertFalse(contains(X509Utilities.CORDA_ROOT_CA))
assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS)) assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS))
assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactlyElementsOf(nodeCaCertPath) assertThat(CertRole.extract(getCertificate(X509Utilities.CORDA_CLIENT_CA))).isEqualTo(CertRole.NODE_CA)
} }
sslKeystore.run { sslKeystore.run {
@ -93,13 +95,13 @@ class NetworkRegistrationHelperTest {
assertThat(nodeTlsCertChain).hasSize(4) assertThat(nodeTlsCertChain).hasSize(4)
// The TLS cert has the same subject as the node CA cert // The TLS cert has the same subject as the node CA cert
assertThat(CordaX500Name.build(nodeTlsCertChain[0].subjectX500Principal)).isEqualTo(nodeLegalName) assertThat(CordaX500Name.build(nodeTlsCertChain[0].subjectX500Principal)).isEqualTo(nodeLegalName)
assertThat(nodeTlsCertChain.drop(1)).containsExactlyElementsOf(nodeCaCertPath) assertThat(CertRole.extract(nodeTlsCertChain.first())).isEqualTo(CertRole.TLS)
} }
trustStore.run { trustStore.run {
assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) assertFalse(contains(X509Utilities.CORDA_CLIENT_CA))
assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(nodeCaCertPath.last()) assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(rootAndIntermediateCA.first.certificate)
} }
} }
@ -107,7 +109,7 @@ class NetworkRegistrationHelperTest {
fun `missing truststore`() { fun `missing truststore`() {
val nodeCaCertPath = createNodeCaCertPath() val nodeCaCertPath = createNodeCaCertPath()
assertThatThrownBy { assertThatThrownBy {
createRegistrationHelper(nodeCaCertPath) createFixedResponseRegistrationHelper(nodeCaCertPath)
}.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.") }.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.")
} }
@ -115,7 +117,7 @@ class NetworkRegistrationHelperTest {
fun `node CA with incorrect cert role`() { fun `node CA with incorrect cert role`() {
val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS) val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS)
saveNetworkTrustStore(nodeCaCertPath.last()) saveNetworkTrustStore(nodeCaCertPath.last())
val registrationHelper = createRegistrationHelper(nodeCaCertPath) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java) assertThatExceptionOfType(CertificateRequestException::class.java)
.isThrownBy { registrationHelper.buildKeystore() } .isThrownBy { registrationHelper.buildKeystore() }
.withMessageContaining(CertificateType.TLS.toString()) .withMessageContaining(CertificateType.TLS.toString())
@ -126,7 +128,7 @@ class NetworkRegistrationHelperTest {
val invalidName = CordaX500Name("Foo", "MU", "GB") val invalidName = CordaX500Name("Foo", "MU", "GB")
val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName) val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName)
saveNetworkTrustStore(nodeCaCertPath.last()) saveNetworkTrustStore(nodeCaCertPath.last())
val registrationHelper = createRegistrationHelper(nodeCaCertPath) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java) assertThatExceptionOfType(CertificateRequestException::class.java)
.isThrownBy { registrationHelper.buildKeystore() } .isThrownBy { registrationHelper.buildKeystore() }
.withMessageContaining(invalidName.toString()) .withMessageContaining(invalidName.toString())
@ -138,7 +140,8 @@ class NetworkRegistrationHelperTest {
X500Principal("O=Foo,L=MU,C=GB"), X500Principal("O=Foo,L=MU,C=GB"),
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
saveNetworkTrustStore(wrongRootCert) saveNetworkTrustStore(wrongRootCert)
val registrationHelper = createRegistrationHelper(createNodeCaCertPath())
val registrationHelper = createRegistrationHelper()
assertThatThrownBy { assertThatThrownBy {
registrationHelper.buildKeystore() registrationHelper.buildKeystore()
}.isInstanceOf(CertPathValidatorException::class.java) }.isInstanceOf(CertPathValidatorException::class.java)
@ -150,10 +153,9 @@ class NetworkRegistrationHelperTest {
assertThat(config.sslKeystore).doesNotExist() assertThat(config.sslKeystore).doesNotExist()
assertThat(config.trustStoreFile).doesNotExist() assertThat(config.trustStoreFile).doesNotExist()
val serviceIdentityCertPath = createServiceIdentityCertPath() val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(it.first.certificate) }
saveNetworkTrustStore(serviceIdentityCertPath.last()) createRegistrationHelper(CertRole.SERVICE_IDENTITY, rootAndIntermediateCA).buildKeystore()
createRegistrationHelper(serviceIdentityCertPath, CertRole.SERVICE_IDENTITY).buildKeystore()
val nodeKeystore = config.loadNodeKeyStore() val nodeKeystore = config.loadNodeKeyStore()
@ -167,42 +169,52 @@ class NetworkRegistrationHelperTest {
assertFalse(contains(X509Utilities.CORDA_ROOT_CA)) assertFalse(contains(X509Utilities.CORDA_ROOT_CA))
assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS)) assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS))
assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) assertFalse(contains(X509Utilities.CORDA_CLIENT_CA))
assertThat(getCertificateChain(serviceIdentityAlias)).containsExactlyElementsOf(serviceIdentityCertPath) assertThat(CertRole.extract(getCertificate(serviceIdentityAlias))).isEqualTo(CertRole.SERVICE_IDENTITY)
} }
} }
private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA, private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA,
legalName: CordaX500Name = nodeLegalName): List<X509Certificate> { legalName: CordaX500Name = nodeLegalName,
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() publicKey: PublicKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME).public,
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) rootAndIntermediateCA: Pair<CertificateAndKeyPair, CertificateAndKeyPair> = createDevIntermediateCaCertPath()): List<X509Certificate> {
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf()) val (rootCa, intermediateCa) = rootAndIntermediateCA
val nameConstraints = if (type == CertificateType.NODE_CA) {
NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
} else {
null
}
val nodeCaCert = X509Utilities.createCertificate( val nodeCaCert = X509Utilities.createCertificate(
type, type,
intermediateCa.certificate, intermediateCa.certificate,
intermediateCa.keyPair, intermediateCa.keyPair,
legalName.x500Principal, legalName.x500Principal,
keyPair.public, publicKey,
nameConstraints = nameConstraints) nameConstraints = nameConstraints)
return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate)
} }
private fun createServiceIdentityCertPath(type: CertificateType = CertificateType.SERVICE_IDENTITY, private fun createFixedResponseRegistrationHelper(response: List<X509Certificate>, certRole: CertRole = CertRole.NODE_CA): NetworkRegistrationHelper {
legalName: CordaX500Name = nodeLegalName): List<X509Certificate> { return createRegistrationHelper(certRole) { response }
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
val keyPair = Crypto.generateKeyPair()
val serviceIdentityCert = X509Utilities.createCertificate(
type,
intermediateCa.certificate,
intermediateCa.keyPair,
legalName.x500Principal,
keyPair.public)
return listOf(serviceIdentityCert, intermediateCa.certificate, rootCa.certificate)
} }
private fun createRegistrationHelper(response: List<X509Certificate>, certRole: CertRole = CertRole.NODE_CA): NetworkRegistrationHelper { private fun createRegistrationHelper(certRole: CertRole = CertRole.NODE_CA, rootAndIntermediateCA: Pair<CertificateAndKeyPair, CertificateAndKeyPair> = createDevIntermediateCaCertPath()) = createRegistrationHelper(certRole) {
val certType = CertificateType.values().first { it.role == certRole }
createNodeCaCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType)
}
private fun createRegistrationHelper(certRole: CertRole = CertRole.NODE_CA, dynamicResponse: (JcaPKCS10CertificationRequest) -> List<X509Certificate>): NetworkRegistrationHelper {
val certService = rigorousMock<NetworkRegistrationService>().also { val certService = rigorousMock<NetworkRegistrationService>().also {
doReturn(requestId).whenever(it).submitRequest(any()) val requests = mutableMapOf<String, JcaPKCS10CertificationRequest>()
doReturn(CertificateResponse(5.seconds, response)).whenever(it).retrieveCertificates(eq(requestId)) doAnswer {
val requestId = SecureHash.randomSHA256().toString()
val request = JcaPKCS10CertificationRequest(it.getArgument<PKCS10CertificationRequest>(0))
requests[requestId] = request
requestId
}.whenever(it).submitRequest(any())
doAnswer {
CertificateResponse(5.seconds, dynamicResponse(requests[it.getArgument(0)]!!))
}.whenever(it).retrieveCertificates(any())
} }
return when (certRole) { return when (certRole) {