Added various X509 utilities to remove some of the existing boilerplate. (#2416)

This commit is contained in:
Shams Asari
2018-01-24 18:07:29 +00:00
committed by GitHub
parent 3c95772288
commit 0fa6969d5d
20 changed files with 156 additions and 131 deletions

View File

@ -62,7 +62,7 @@ fun X509KeyStore.storeLegalIdentity(alias: String, keyPair: KeyPair = Crypto.gen
val identityCertPath = listOf(identityCert) + nodeCaCertPath
setPrivateKey(alias, keyPair.private, identityCertPath)
save()
return PartyAndCertificate(X509CertificateFactory().generateCertPath(identityCertPath))
return PartyAndCertificate(X509Utilities.buildCertPath(identityCertPath))
}
fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair {

View File

@ -13,7 +13,13 @@ import java.security.cert.X509Certificate
*/
class X509KeyStore private constructor(val internal: KeyStore, private val storePassword: String, private val keyStoreFile: Path? = null) {
/** Wrap an existing [KeyStore]. [save] is not supported. */
constructor(internal: KeyStore, storePassword: String) : this(internal, storePassword, null)
constructor(keyStore: KeyStore, storePassword: String) : this(keyStore, storePassword, null)
/** Create an empty [KeyStore] using the given password. [save] is not supported. */
constructor(storePassword: String) : this(
KeyStore.getInstance(KEYSTORE_TYPE).apply { load(null, storePassword.toCharArray()) },
storePassword
)
companion object {
/**
@ -49,12 +55,10 @@ class X509KeyStore private constructor(val internal: KeyStore, private val store
}
fun setPrivateKey(alias: String, key: PrivateKey, certificates: List<X509Certificate>, keyPassword: String = storePassword) {
checkWritableToFile()
internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray())
}
fun setCertificate(alias: String, certificate: X509Certificate) {
checkWritableToFile()
internal.setCertificateEntry(alias, certificate)
}

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.random63BitValue
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
@ -93,15 +94,19 @@ object X509Utilities {
return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
}
// TODO Provide an overload which takes in a List or a CertPath
@Throws(CertPathValidatorException::class)
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: X509Certificate) {
validateCertificateChain(trustedRoot, certificates.asList())
}
fun validateCertificateChain(trustedRoot: X509Certificate, certificates: List<X509Certificate>) {
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
validateCertPath(trustedRoot, buildCertPath(certificates))
}
fun validateCertPath(trustedRoot: X509Certificate, certPath: CertPath) {
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
params.isRevocationEnabled = false
val certPath = X509CertificateFactory().generateCertPath(*certificates)
val pathValidator = CertPathValidator.getInstance("PKIX")
pathValidator.validate(certPath, params)
CertPathValidator.getInstance("PKIX").validate(certPath, params)
}
/**
@ -265,6 +270,21 @@ object X509Utilities {
fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair): PKCS10CertificationRequest {
return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME)
}
fun buildCertPath(first: X509Certificate, remaining: List<X509Certificate>): CertPath {
val certificates = ArrayList<X509Certificate>(1 + remaining.size)
certificates += first
certificates += remaining
return buildCertPath(certificates)
}
fun buildCertPath(vararg certificates: X509Certificate): CertPath {
return X509CertificateFactory().generateCertPath(*certificates)
}
fun buildCertPath(certificates: List<X509Certificate>): CertPath {
return X509CertificateFactory().generateCertPath(certificates)
}
}
/**
@ -275,6 +295,16 @@ object X509Utilities {
fun X509Certificate.toBc() = X509CertificateHolder(encoded)
fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().generateCertificate(encoded.inputStream())
val CertPath.x509Certificates: List<X509Certificate> get() {
require(type == "X.509") { "Not an X.509 cert path: $this" }
// We're not mapping the list to avoid creating a new one.
return uncheckedCast(certificates)
}
val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certificate) { "Not an X.509 certificate: $this" }
val Array<Certificate>.x509: List<X509Certificate> get() = map { it.x509 }
/**
* Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best
* so assume this class is not.
@ -282,19 +312,11 @@ fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().ge
class X509CertificateFactory {
val delegate: CertificateFactory = CertificateFactory.getInstance("X.509")
fun generateCertificate(input: InputStream): X509Certificate {
return delegate.generateCertificate(input) as X509Certificate
}
fun generateCertificate(input: InputStream): X509Certificate = delegate.generateCertificate(input).x509
// TODO X509Certificate
fun generateCertPath(certificates: List<Certificate>): CertPath {
return delegate.generateCertPath(certificates)
}
fun generateCertPath(vararg certificates: X509Certificate): CertPath = generateCertPath(certificates.asList())
// TODO X509Certificate
fun generateCertPath(vararg certificates: Certificate): CertPath {
return delegate.generateCertPath(certificates.asList())
}
fun generateCertPath(certificates: List<X509Certificate>): CertPath = delegate.generateCertPath(certificates)
}
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean, val role: CertRole?) {

View File

@ -266,10 +266,10 @@ class X509UtilitiesTest {
assertTrue(clientSocket.isConnected)
// Double check hostname manually
val peerChain = clientSocket.session.peerCertificates
val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
val peerChain = clientSocket.session.peerCertificates.x509
val peerX500Principal = peerChain[0].subjectX500Principal
assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal)
X509Utilities.validateCertificateChain(rootCa.certificate, *peerChain)
X509Utilities.validateCertificateChain(rootCa.certificate, peerChain)
val output = DataOutputStream(clientSocket.outputStream)
output.writeUTF("Hello World")
var timeout = 0
@ -338,7 +338,7 @@ class X509UtilitiesTest {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey)
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey)
val expected = X509CertificateFactory().generateCertPath(certificate, rootCACert)
val expected = X509Utilities.buildCertPath(certificate, rootCACert)
val serialized = expected.serialize(factory, context).bytes
val actual: CertPath = serialized.deserialize(factory, context)
assertEquals(expected, actual)