mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
Adding CSR signature verification (#537)
This commit is contained in:
parent
34800ab527
commit
341e060424
@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user