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.post(serializedData: OpaqueBytes) {
openHttpConnection().apply {
fun URL.post(serializedData: OpaqueBytes): ByteArray {
return openHttpConnection().run {
doOutput = true
requestMethod = "POST"
setRequestProperty("Content-Type", "application/octet-stream")
outputStream.use { serializedData.open().copyTo(it) }
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_INTERMEDIATE_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 java.io.ByteArrayOutputStream
import java.io.InputStream
@ -43,8 +45,11 @@ class RegistrationWebService(private val csrHandler: CsrHandler, private val cli
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.TEXT_PLAIN)
fun submitRequest(input: InputStream): Response {
val certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) }
val requestId = csrHandler.saveRequest(certificationRequest)
val csr = input.use { JcaPKCS10CertificationRequest(it.readBytes()) }
if (!csr.isSignatureValid()) {
return status(Response.Status.BAD_REQUEST).entity("Invalid CSR signature").build()
}
val requestId = csrHandler.saveRequest(csr)
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.doorman.NetworkManagementWebServer
import com.r3.corda.networkmanage.doorman.signer.CsrHandler
import net.corda.core.CordaOID
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
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.OpaqueBytes
import net.corda.core.utilities.seconds
import net.corda.node.utilities.registration.cacheControl
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.crypto.*
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.apache.commons.io.IOUtils
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.style.BCStyle
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.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -43,13 +48,14 @@ import java.net.HttpURLConnection
import java.net.HttpURLConnection.*
import java.net.URL
import java.nio.charset.StandardCharsets.UTF_8
import java.security.KeyPair
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.util.*
import java.util.zip.ZipInputStream
import javax.security.auth.x500.X500Principal
import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class RegistrationWebServiceTest : TestBase() {
private lateinit var webServer: NetworkManagementWebServer
@ -75,7 +81,7 @@ class RegistrationWebServiceTest : TestBase() {
}
@Test
fun `submit request`() {
fun `submit request succeeds`() {
val id = SecureHash.randomSHA256().toString()
val requestProcessor = mock<CsrHandler> {
@ -97,6 +103,22 @@ class RegistrationWebServiceTest : TestBase() {
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
fun `retrieve certificate`() {
val keyPair = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
@ -213,12 +235,7 @@ class RegistrationWebServiceTest : TestBase() {
}
private fun submitRequest(request: PKCS10CertificationRequest): String {
val conn = URL("http://${webServer.hostAndPort}/certificate").openConnection() as HttpURLConnection
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() }
return String(URL("http://${webServer.hostAndPort}/certificate").post(OpaqueBytes(request.encoded)))
}
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 {
data class NotReady(val pollInterval: Int) : 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.SignatureScheme
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.millis
import org.bouncycastle.asn1.*
@ -36,6 +39,7 @@ import java.math.BigInteger
import java.nio.file.Path
import java.security.KeyPair
import java.security.PublicKey
import java.security.SignatureException
import java.security.cert.*
import java.security.cert.Certificate
import java.time.Duration
@ -275,7 +279,11 @@ object X509Utilities {
return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public)
.addAttribute(BCStyle.E, DERUTF8String(email))
.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 {
@ -321,6 +329,13 @@ val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certif
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
* so assume this class is not.