Adding CSR signature verification (#537)

This commit is contained in:
Michal Kit 2018-03-14 09:17:09 +00:00 committed by GitHub
parent 34800ab527
commit 341e060424
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 18 deletions

View File

@ -336,13 +336,14 @@ val KClass<*>.packageName: String get() = java.`package`.name
fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection
fun URL.post(serializedData: OpaqueBytes) { fun URL.post(serializedData: OpaqueBytes): ByteArray {
openHttpConnection().apply { return openHttpConnection().run {
doOutput = true doOutput = true
requestMethod = "POST" requestMethod = "POST"
setRequestProperty("Content-Type", "application/octet-stream") setRequestProperty("Content-Type", "application/octet-stream")
outputStream.use { serializedData.open().copyTo(it) } outputStream.use { serializedData.open().copyTo(it) }
checkOkResponse() checkOkResponse()
inputStream.use { it.readBytes() }
} }
} }

View File

@ -15,6 +15,8 @@ import com.r3.corda.networkmanage.doorman.signer.CsrHandler
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.isSignatureValid
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
@ -43,8 +45,11 @@ class RegistrationWebService(private val csrHandler: CsrHandler, private val cli
@Consumes(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN)
fun submitRequest(input: InputStream): Response { fun submitRequest(input: InputStream): Response {
val certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) } val csr = input.use { JcaPKCS10CertificationRequest(it.readBytes()) }
val requestId = csrHandler.saveRequest(certificationRequest) if (!csr.isSignatureValid()) {
return status(Response.Status.BAD_REQUEST).entity("Invalid CSR signature").build()
}
val requestId = csrHandler.saveRequest(csr)
return ok(requestId).build() return ok(requestId).build()
} }

View File

@ -15,26 +15,31 @@ import com.r3.corda.networkmanage.TestBase
import com.r3.corda.networkmanage.common.persistence.CertificateResponse import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.doorman.NetworkManagementWebServer import com.r3.corda.networkmanage.doorman.NetworkManagementWebServer
import com.r3.corda.networkmanage.doorman.signer.CsrHandler import com.r3.corda.networkmanage.doorman.signer.CsrHandler
import net.corda.core.CordaOID
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole
import net.corda.core.internal.post
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.utilities.registration.cacheControl import net.corda.node.utilities.registration.cacheControl
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.*
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.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.DERUTF8String
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.style.BCStyle
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.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -43,13 +48,14 @@ import java.net.HttpURLConnection
import java.net.HttpURLConnection.* import java.net.HttpURLConnection.*
import java.net.URL import java.net.URL
import java.nio.charset.StandardCharsets.UTF_8 import java.nio.charset.StandardCharsets.UTF_8
import java.security.KeyPair
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.* import java.util.*
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class RegistrationWebServiceTest : TestBase() { class RegistrationWebServiceTest : TestBase() {
private lateinit var webServer: NetworkManagementWebServer private lateinit var webServer: NetworkManagementWebServer
@ -75,7 +81,7 @@ class RegistrationWebServiceTest : TestBase() {
} }
@Test @Test
fun `submit request`() { fun `submit request succeeds`() {
val id = SecureHash.randomSHA256().toString() val id = SecureHash.randomSHA256().toString()
val requestProcessor = mock<CsrHandler> { val requestProcessor = mock<CsrHandler> {
@ -97,6 +103,22 @@ class RegistrationWebServiceTest : TestBase() {
verify(requestProcessor, times(2)).saveRequest(any()) verify(requestProcessor, times(2)).saveRequest(any())
} }
@Test
fun `submit request fails with invalid public key`() {
startSigningServer(mock())
val keyPairGenuine = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val keyPairMalicious = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val request = createUnverifiedCertificateSigningRequest(
CordaX500Name(locality = "London", organisation = "Legal Name", country = "GB").x500Principal,
"my@mail.com",
KeyPair(keyPairMalicious.public, keyPairGenuine.private))
// Post request to signing server via http.
assertFailsWith<IOException>("Invalid CSR signature") {
submitRequest(request)
}
}
@Test @Test
fun `retrieve certificate`() { fun `retrieve certificate`() {
val keyPair = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val keyPair = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
@ -213,12 +235,7 @@ class RegistrationWebServiceTest : TestBase() {
} }
private fun submitRequest(request: PKCS10CertificationRequest): String { private fun submitRequest(request: PKCS10CertificationRequest): String {
val conn = URL("http://${webServer.hostAndPort}/certificate").openConnection() as HttpURLConnection return String(URL("http://${webServer.hostAndPort}/certificate").post(OpaqueBytes(request.encoded)))
conn.doOutput = true
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM)
conn.outputStream.write(request.encoded)
return conn.inputStream.bufferedReader().use { it.readLine() }
} }
private fun pollForResponse(id: String): PollResponse { private fun pollForResponse(id: String): PollResponse {
@ -241,6 +258,14 @@ class RegistrationWebServiceTest : TestBase() {
} }
} }
private fun createUnverifiedCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair): PKCS10CertificationRequest {
val signer = ContentSignerBuilder.build(DEFAULT_TLS_SIGNATURE_SCHEME, keyPair.private, Crypto.findProvider(DEFAULT_TLS_SIGNATURE_SCHEME.providerName))
return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public)
.addAttribute(BCStyle.E, DERUTF8String(email))
.addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), CertRole.NODE_CA)
.build(signer)
}
private interface PollResponse { private interface PollResponse {
data class NotReady(val pollInterval: Int) : PollResponse data class NotReady(val pollInterval: Int) : PollResponse
data class Ready(val certChain: List<X509Certificate>) : PollResponse data class Ready(val certChain: List<X509Certificate>) : PollResponse

View File

@ -14,7 +14,10 @@ import net.corda.core.CordaOID
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.* import net.corda.core.internal.CertRole
import net.corda.core.internal.reader
import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.writer
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import org.bouncycastle.asn1.* import org.bouncycastle.asn1.*
@ -36,6 +39,7 @@ import java.math.BigInteger
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException
import java.security.cert.* import java.security.cert.*
import java.security.cert.Certificate import java.security.cert.Certificate
import java.time.Duration import java.time.Duration
@ -275,7 +279,11 @@ object X509Utilities {
return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public) return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public)
.addAttribute(BCStyle.E, DERUTF8String(email)) .addAttribute(BCStyle.E, DERUTF8String(email))
.addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole) .addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole)
.build(signer) .build(signer).apply {
if (!isSignatureValid()) {
throw SignatureException("The certificate signing request signature validation failed.")
}
}
} }
fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest { fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest {
@ -321,6 +329,13 @@ val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certif
val Array<Certificate>.x509: List<X509Certificate> get() = map { it.x509 } val Array<Certificate>.x509: List<X509Certificate> get() = map { it.x509 }
/**
* Validates the signature of the CSR
*/
fun PKCS10CertificationRequest.isSignatureValid(): Boolean {
return this.isSignatureValid(JcaContentVerifierProviderBuilder().build(this.subjectPublicKeyInfo))
}
/** /**
* Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best * Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best
* so assume this class is not. * so assume this class is not.