mirror of
https://github.com/corda/corda.git
synced 2025-04-09 04:15:35 +00:00
faster key encoding/decoding and generic converters between key implementations
This commit is contained in:
parent
1bc4c490bc
commit
53276c1f06
@ -21,13 +21,20 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateKey
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey
|
||||
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
|
||||
import org.bouncycastle.jce.ECNamedCurveTable
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
|
||||
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
|
||||
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
|
||||
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
|
||||
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
|
||||
import sun.security.pkcs.PKCS8Key
|
||||
import sun.security.util.DerValue
|
||||
import sun.security.x509.X509Key
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.KeyFactory
|
||||
@ -140,6 +147,10 @@ object Crypto {
|
||||
SPHINCS256_SHA256
|
||||
).associateBy { it.schemeCodeName }
|
||||
|
||||
// We need to group signature schemes per algorithm, so to quickly identify them during decoding.
|
||||
// Please note there are schemes with the same algorithm, e.g. EC (or ECDSA) keys are used for both ECDSA_SECP256K1_SHA256 and ECDSA_SECP256R1_SHA256.
|
||||
private val algorithmGroups = supportedSignatureSchemes.values.groupBy { it.algorithmName }
|
||||
|
||||
// This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider
|
||||
// that could cause unexpected and suspicious behaviour.
|
||||
// i.e. if someone removes a Provider and then he/she adds a new one with the same name.
|
||||
@ -167,37 +178,20 @@ object Crypto {
|
||||
* @return a currently supported SignatureScheme.
|
||||
* @throws IllegalArgumentException if the requested signature scheme is not supported.
|
||||
*/
|
||||
fun findSignatureScheme(schemeCodeName: String): SignatureScheme = supportedSignatureSchemes[schemeCodeName] ?: throw IllegalArgumentException("Unsupported key/algorithm for metadata schemeCodeName: $schemeCodeName")
|
||||
fun findSignatureScheme(schemeCodeName: String): SignatureScheme = supportedSignatureSchemes[schemeCodeName] ?: throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $schemeCodeName")
|
||||
|
||||
/**
|
||||
* Retrieve the corresponding [SignatureScheme] based on the type of the input [Key].
|
||||
* This function is usually called when requiring to verify signatures and the signing schemes must be defined.
|
||||
* Note that only the Corda platform standard schemes are supported (see [Crypto]).
|
||||
* Note that we always need to add an additional if-else statement when there are signature schemes
|
||||
* with the same algorithmName, but with different parameters (e.g. now there are two ECDSA schemes, each using its own curve).
|
||||
* For the supported signature schemes see [Crypto].
|
||||
* @param key either private or public.
|
||||
* @return a currently supported SignatureScheme.
|
||||
* @throws IllegalArgumentException if the requested key type is not supported.
|
||||
*/
|
||||
fun findSignatureScheme(key: Key): SignatureScheme {
|
||||
for (sig in supportedSignatureSchemes.values) {
|
||||
var algorithm = key.algorithm
|
||||
if (algorithm == "EC") algorithm = "ECDSA" // required to read ECC keys from Keystore, because encoding may change algorithm name from ECDSA to EC.
|
||||
if (algorithm == "SPHINCS-256") algorithm = "SPHINCS256" // because encoding may change algorithm name from SPHINCS256 to SPHINCS-256.
|
||||
if (algorithm == sig.algorithmName) {
|
||||
// If more than one ECDSA schemes are supported, we should distinguish between them by checking their curve parameters.
|
||||
if (algorithm == "EdDSA") {
|
||||
if ((key is EdDSAPublicKey && publicKeyOnCurve(sig, key)) || (key is EdDSAPrivateKey && key.params == sig.algSpec)) {
|
||||
return sig
|
||||
} else break // use continue if in the future we support more than one Edwards curves.
|
||||
} else if (algorithm == "ECDSA") {
|
||||
if ((key is BCECPublicKey && publicKeyOnCurve(sig, key)) || (key is BCECPrivateKey && key.parameters == sig.algSpec)) {
|
||||
return sig
|
||||
} else continue
|
||||
} else return sig // it's either RSA_SHA256 or SPHINCS-256.
|
||||
}
|
||||
}
|
||||
throw IllegalArgumentException("Unsupported key/algorithm for the key: ${key.encoded.toBase58()}")
|
||||
val algorithm = matchingAlgorithmName(key.algorithm)
|
||||
algorithmGroups[algorithm]?.filter { validateKey(it, key) }?.firstOrNull { return it }
|
||||
throw IllegalArgumentException("Unsupported key algorithm: ${key.algorithm} or invalid key format")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,11 +203,16 @@ object Crypto {
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
|
||||
for ((_, _, _, providerName, algorithmName) in supportedSignatureSchemes.values) {
|
||||
val algorithm = matchingAlgorithmName(PKCS8Key.parseKey(DerValue(encodedKey)).algorithm)
|
||||
// There are cases where the same key algorithm is applied to different signature schemes.
|
||||
// Currently, this occurs with ECDSA as it applies to either secp256K1 or secp256R1 curves.
|
||||
// In such a case, we should try and identify which of the candidate schemes is the correct one so as
|
||||
// to generate the appropriate key.
|
||||
for (signatureScheme in algorithmGroups[algorithm]!!) {
|
||||
try {
|
||||
return KeyFactory.getInstance(algorithmName, providerMap[providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
|
||||
return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
|
||||
} catch (ikse: InvalidKeySpecException) {
|
||||
// ignore it - only used to bypass the scheme that causes an exception.
|
||||
// ignore it - only used to bypass the scheme that causes an exception, as it has the same name, but different params.
|
||||
}
|
||||
}
|
||||
throw IllegalArgumentException("This private key cannot be decoded, please ensure it is PKCS8 encoded and the signature scheme is supported.")
|
||||
@ -258,11 +257,16 @@ object Crypto {
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun decodePublicKey(encodedKey: ByteArray): PublicKey {
|
||||
for ((_, _, _, providerName, algorithmName) in supportedSignatureSchemes.values) {
|
||||
val algorithm = matchingAlgorithmName(X509Key.parse(DerValue(encodedKey)).algorithm)
|
||||
// There are cases where the same key algorithm is applied to different signature schemes.
|
||||
// Currently, this occurs with ECDSA as it applies to either secp256K1 or secp256R1 curves.
|
||||
// In such a case, we should try and identify which of the candidate schemes is the correct one so as
|
||||
// to generate the appropriate key.
|
||||
for (signatureScheme in algorithmGroups[algorithm]!!) {
|
||||
try {
|
||||
return KeyFactory.getInstance(algorithmName, providerMap[providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
|
||||
return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
|
||||
} catch (ikse: InvalidKeySpecException) {
|
||||
// ignore it - only used to bypass the scheme that causes an exception.
|
||||
// ignore it - only used to bypass the scheme that causes an exception, as it has the same name, but different params.
|
||||
}
|
||||
}
|
||||
throw IllegalArgumentException("This public key cannot be decoded, please ensure it is X509 encoded and the signature scheme is supported.")
|
||||
@ -273,7 +277,7 @@ object Crypto {
|
||||
* This should be used when the type key is known, e.g. during Kryo deserialisation or with key caches or key managers.
|
||||
* @param schemeCodeName a [String] that should match a key in supportedSignatureSchemes map (e.g. ECDSA_SECP256K1_SHA256).
|
||||
* @param encodedKey an X509 encoded public key.
|
||||
* @throws IllegalArgumentException if the requested scheme is not supported
|
||||
* @throws IllegalArgumentException if the requested scheme is not supported.
|
||||
* @throws InvalidKeySpecException if the given key specification
|
||||
* is inappropriate for this key factory to produce a public key.
|
||||
*/
|
||||
@ -285,7 +289,7 @@ object Crypto {
|
||||
* This should be used when the type key is known, e.g. during Kryo deserialisation or with key caches or key managers.
|
||||
* @param signatureScheme a signature scheme (e.g. ECDSA_SECP256K1_SHA256).
|
||||
* @param encodedKey an X509 encoded public key.
|
||||
* @throws IllegalArgumentException if the requested scheme is not supported
|
||||
* @throws IllegalArgumentException if the requested scheme is not supported.
|
||||
* @throws InvalidKeySpecException if the given key specification
|
||||
* is inappropriate for this key factory to produce a public key.
|
||||
*/
|
||||
@ -444,7 +448,7 @@ object Crypto {
|
||||
*/
|
||||
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
|
||||
fun doVerify(publicKey: PublicKey, transactionSignature: TransactionSignature): Boolean {
|
||||
if (publicKey != transactionSignature.metaData.publicKey) IllegalArgumentException("MetaData's publicKey: ${transactionSignature.metaData.publicKey.encoded.toBase58()} does not match the input clearData: ${publicKey.encoded.toBase58()}")
|
||||
if (publicKey != transactionSignature.metaData.publicKey) IllegalArgumentException("MetaData's publicKey: ${transactionSignature.metaData.publicKey.toStringShort()} does not match")
|
||||
return Crypto.doVerify(publicKey, transactionSignature.signatureData, transactionSignature.metaData.bytes())
|
||||
}
|
||||
|
||||
@ -598,9 +602,9 @@ object Crypto {
|
||||
* Check if a point's coordinates are on the expected curve to avoid certain types of ECC attacks.
|
||||
* Point-at-infinity is not permitted as well.
|
||||
* @see <a href="https://safecurves.cr.yp.to/twist.html">Small subgroup and invalid-curve attacks</a> for a more descriptive explanation on such attacks.
|
||||
* We use this function on [findSignatureScheme] for a [PublicKey]; currently used for signature verification only.
|
||||
* We use this function on [validatePublicKey], which is currently used for signature verification only.
|
||||
* Thus, as these attacks are mostly not relevant to signature verification, we should note that
|
||||
* we're doing it out of an abundance of caution and specifically to proactively protect developers
|
||||
* we are doing it out of an abundance of caution and specifically to proactively protect developers
|
||||
* against using these points as part of a DH key agreement or for use cases as yet unimagined.
|
||||
* This method currently applies to BouncyCastle's ECDSA (both R1 and K1 curves) and I2P's EdDSA (ed25519 curve).
|
||||
* @param publicKey a [PublicKey], usually used to validate a signer's public key in on the Curve.
|
||||
@ -625,4 +629,66 @@ object Crypto {
|
||||
|
||||
/** Check if the requested [SignatureScheme] is supported by the system. */
|
||||
fun isSupportedSignatureScheme(signatureScheme: SignatureScheme): Boolean = supportedSignatureSchemes[signatureScheme.schemeCodeName] === signatureScheme
|
||||
|
||||
// map algorithm names returned from Keystore (or after encode/decode) to the supported algorithm names.
|
||||
private fun matchingAlgorithmName(algorithm: String): String {
|
||||
return when (algorithm) {
|
||||
"EC" -> "ECDSA"
|
||||
"SPHINCS-256" -> "SPHINCS256"
|
||||
"1.3.6.1.4.1.22554.2.1" -> "SPHINCS256" // Unfortunately, PKCS8Key and X509Key parsing return the OID as the algorithm name and not SPHINCS256.
|
||||
else -> algorithm
|
||||
}
|
||||
}
|
||||
|
||||
// validate a key, by checking its algorithmic params.
|
||||
private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean {
|
||||
return when (key) {
|
||||
is PublicKey -> validatePublicKey(signatureScheme, key)
|
||||
is PrivateKey -> validatePrivateKey(signatureScheme, key)
|
||||
else -> throw IllegalArgumentException("Unsupported key type: ${key::class}")
|
||||
}
|
||||
}
|
||||
|
||||
// check if a public key satisfies algorithm specs (for ECC: key should lie on the curve and not being point-at-infinity).
|
||||
private fun validatePublicKey(signatureScheme: SignatureScheme, key: PublicKey): Boolean {
|
||||
when (key) {
|
||||
is BCECPublicKey, is EdDSAPublicKey -> return publicKeyOnCurve(signatureScheme, key)
|
||||
is BCRSAPublicKey, is BCSphincs256PublicKey -> return true // TODO: Check if non-ECC keys satisfy params (i.e. approved/valid RSA modulus size).
|
||||
else -> throw IllegalArgumentException("Unsupported key type: ${key::class}")
|
||||
}
|
||||
}
|
||||
|
||||
// check if a private key satisfies algorithm specs.
|
||||
private fun validatePrivateKey(signatureScheme: SignatureScheme, key: PrivateKey): Boolean {
|
||||
when (key) {
|
||||
is BCECPrivateKey -> return key.parameters == signatureScheme.algSpec
|
||||
is EdDSAPrivateKey -> return key.params == signatureScheme.algSpec
|
||||
is BCRSAPrivateKey, is BCSphincs256PrivateKey -> return true // TODO: Check if non-ECC keys satisfy params (i.e. approved/valid RSA modulus size).
|
||||
else -> throw IllegalArgumentException("Unsupported key type: ${key::class}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a public key to a supported implementation. This can be used to convert a SUN's EC key to an BC key.
|
||||
* This method is usually required to retrieve a key (via its corresponding cert) from JKS keystores that by default return SUN implementations.
|
||||
* @param key a public key.
|
||||
* @return a supported implementation of the input public key.
|
||||
* @throws IllegalArgumentException on not supported scheme or if the given key specification
|
||||
* is inappropriate for a supported key factory to produce a private key.
|
||||
*/
|
||||
fun toSupportedPublicKey(key: PublicKey): PublicKey {
|
||||
return Crypto.decodePublicKey(key.encoded)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a private key to a supported implementation. This can be used to convert a SUN's EC key to an BC key.
|
||||
* This method is usually required to retrieve keys from JKS keystores that by default return SUN implementations.
|
||||
* @param key a private key.
|
||||
* @return a supported implementation of the input private key.
|
||||
* @throws IllegalArgumentException on not supported scheme or if the given key specification
|
||||
* is inappropriate for a supported key factory to produce a private key.
|
||||
*/
|
||||
fun toSupportedPrivateKey(key: PrivateKey): PrivateKey {
|
||||
return Crypto.decodePrivateKey(key.encoded)
|
||||
}
|
||||
}
|
||||
|
@ -71,11 +71,9 @@ fun KeyPair.sign(bytesToSign: OpaqueBytes, party: Party) = sign(bytesToSign.byte
|
||||
// implementation of CompositeSignature.
|
||||
@Throws(InvalidKeyException::class)
|
||||
fun KeyPair.sign(bytesToSign: ByteArray, party: Party): DigitalSignature.LegallyIdentifiable {
|
||||
// Quick workaround when we have CompositeKey as Party owningKey.
|
||||
if (party.owningKey is CompositeKey) throw InvalidKeyException("Signing for parties with CompositeKey not supported.")
|
||||
val sig = sign(bytesToSign)
|
||||
val sigKey = when (party.owningKey) { // Quick workaround when we have CompositeKey as Party owningKey.
|
||||
is CompositeKey -> throw InvalidKeyException("Signing for parties with CompositeKey not supported.")
|
||||
else -> party.owningKey
|
||||
}
|
||||
return DigitalSignature.LegallyIdentifiable(party, sig.bytes)
|
||||
}
|
||||
|
||||
|
@ -16,10 +16,10 @@ object KeyStoreUtilities {
|
||||
|
||||
/**
|
||||
* Helper method to either open an existing keystore for modification, or create a new blank keystore.
|
||||
* @param keyStoreFilePath location of KeyStore file
|
||||
* @param keyStoreFilePath location of KeyStore file.
|
||||
* @param storePassword password to open the store. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
* @return returns the KeyStore opened/created
|
||||
* @return returns the KeyStore opened/created.
|
||||
*/
|
||||
fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore {
|
||||
val pass = storePassword.toCharArray()
|
||||
@ -34,11 +34,11 @@ object KeyStoreUtilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to open an existing keystore for modification/read
|
||||
* @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException
|
||||
* Helper method to open an existing keystore for modification/read.
|
||||
* @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException.
|
||||
* @param storePassword password to open the store. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
* @return returns the KeyStore opened
|
||||
* @return returns the KeyStore opened.
|
||||
* @throws IOException if there was an error reading the key store from the file.
|
||||
* @throws KeyStoreException if the password is incorrect or the key store is damaged.
|
||||
*/
|
||||
@ -48,11 +48,11 @@ object KeyStoreUtilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to open an existing keystore for modification/read
|
||||
* @param input stream containing a KeyStore e.g. loaded from a resource file
|
||||
* Helper method to open an existing keystore for modification/read.
|
||||
* @param input stream containing a KeyStore e.g. loaded from a resource file.
|
||||
* @param storePassword password to open the store. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
* @return returns the KeyStore opened
|
||||
* @return returns the KeyStore opened.
|
||||
* @throws IOException if there was an error reading the key store from the stream.
|
||||
* @throws KeyStoreException if the password is incorrect or the key store is damaged.
|
||||
*/
|
||||
@ -68,12 +68,12 @@ object KeyStoreUtilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper extension method to add, or overwrite any key data in store
|
||||
* @param alias name to record the private key and certificate chain under
|
||||
* @param key cryptographic key to store
|
||||
* Helper extension method to add, or overwrite any key data in store.
|
||||
* @param alias name to record the private key and certificate chain under.
|
||||
* @param key cryptographic key to store.
|
||||
* @param password password for unlocking the key entry in the future. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
* @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert
|
||||
* @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert.
|
||||
*/
|
||||
fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array<Certificate>) {
|
||||
if (containsAlias(alias)) {
|
||||
@ -83,9 +83,9 @@ fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper extension method to add, or overwrite any public certificate data in store
|
||||
* @param alias name to record the public certificate under
|
||||
* @param cert certificate to store
|
||||
* Helper extension method to add, or overwrite any public certificate data in store.
|
||||
* @param alias name to record the public certificate under.
|
||||
* @param cert certificate to store.
|
||||
*/
|
||||
fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) {
|
||||
if (containsAlias(alias)) {
|
||||
@ -96,8 +96,8 @@ fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) {
|
||||
|
||||
|
||||
/**
|
||||
* Helper method save KeyStore to storage
|
||||
* @param keyStoreFilePath the file location to save to
|
||||
* Helper method save KeyStore to storage.
|
||||
* @param keyStoreFilePath the file location to save to.
|
||||
* @param storePassword password to access the store in future. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
*/
|
||||
@ -108,31 +108,47 @@ fun KeyStore.store(out: OutputStream, password: String) = store(out, password.to
|
||||
|
||||
/**
|
||||
* Extract public and private keys from a KeyStore file assuming storage alias is known.
|
||||
* @param keyPassword Password to unlock the private key entries
|
||||
* @param alias The name to lookup the Key and Certificate chain from
|
||||
* @return The KeyPair found in the KeyStore under the specified alias
|
||||
* @param alias The name to lookup the Key and Certificate chain from.
|
||||
* @param keyPassword Password to unlock the private key entries.
|
||||
* @return The KeyPair found in the KeyStore under the specified alias.
|
||||
*/
|
||||
fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertificateAndKey(alias, keyPassword).keyPair
|
||||
fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertificateAndKeyPair(alias, keyPassword).keyPair
|
||||
|
||||
/**
|
||||
* Helper method to load a Certificate and KeyPair from their KeyStore.
|
||||
* The access details should match those of the createCAKeyStoreAndTrustStore call used to manufacture the keys.
|
||||
* @param keyPassword The password for the PrivateKey (not the store access password)
|
||||
* @param alias The name to search for the data. Typically if generated with the methods here this will be one of
|
||||
* CERT_PRIVATE_KEY_ALIAS, ROOT_CA_CERT_PRIVATE_KEY_ALIAS, INTERMEDIATE_CA_PRIVATE_KEY_ALIAS defined above
|
||||
* CERT_PRIVATE_KEY_ALIAS, ROOT_CA_CERT_PRIVATE_KEY_ALIAS, INTERMEDIATE_CA_PRIVATE_KEY_ALIAS defined above.
|
||||
* @param keyPassword The password for the PrivateKey (not the store access password).
|
||||
*/
|
||||
fun KeyStore.getCertificateAndKey(alias: String, keyPassword: String): CertificateAndKey {
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val key = getKey(alias, keyPass) as PrivateKey
|
||||
fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): CertificateAndKeyPair {
|
||||
val cert = getCertificate(alias) as X509Certificate
|
||||
// Using Crypto.decodePublicKey to convert X509Key to bouncy castle public key implementation.
|
||||
// Using Crypto.decodePrivateKey to convert sun provider key implementation to bouncy castle private key implementation.
|
||||
return CertificateAndKey(cert, KeyPair(Crypto.decodePublicKey(cert.publicKey.encoded), Crypto.decodePrivateKey(key.encoded)))
|
||||
return CertificateAndKeyPair(cert, KeyPair(Crypto.toSupportedPublicKey(cert.publicKey), getSupportedKey(alias, keyPassword)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract public X509 certificate from a KeyStore file assuming storage alias is know
|
||||
* @param alias The name to lookup the Key and Certificate chain from
|
||||
* @return The X509Certificate found in the KeyStore under the specified alias
|
||||
* Extract public X509 certificate from a KeyStore file assuming storage alias is known.
|
||||
* @param alias The name to lookup the Key and Certificate chain from.
|
||||
* @return The X509Certificate found in the KeyStore under the specified alias.
|
||||
*/
|
||||
fun KeyStore.getX509Certificate(alias: String): X509Certificate = getCertificate(alias) as X509Certificate
|
||||
|
||||
/**
|
||||
* Extract a private key from a KeyStore file assuming storage alias is known.
|
||||
* By default, a JKS keystore returns PrivateKey implementations supported by the SUN provider.
|
||||
* For instance, if one imports a BouncyCastle ECC key, JKS will return a SUN ECC key implementation on getKey.
|
||||
* To convert to a supported implementation, an encode->decode method is applied to the keystore's returned object.
|
||||
* @param alias The name to lookup the Key.
|
||||
* @param keyPassword Password to unlock the private key entries.
|
||||
* @return the requested private key in supported type.
|
||||
* @throws KeyStoreException if the keystore has not been initialized.
|
||||
* @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found (not supported from the Keystore provider).
|
||||
* @throws UnrecoverableKeyException if the key cannot be recovered (e.g., the given password is wrong).
|
||||
* @throws IllegalArgumentException on not supported scheme or if the given key specification
|
||||
* is inappropriate for a supported key factory to produce a private key.
|
||||
*/
|
||||
fun KeyStore.getSupportedKey(alias: String, keyPassword: String): PrivateKey {
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val key = getKey(alias, keyPass) as PrivateKey
|
||||
return Crypto.toSupportedPrivateKey(key)
|
||||
}
|
||||
|
@ -44,12 +44,12 @@ object X509Utilities {
|
||||
|
||||
private val DEFAULT_VALIDITY_WINDOW = Pair(0, 365 * 10)
|
||||
/**
|
||||
* Helper method to get a notBefore and notAfter pair from current day bounded by parent certificate validity range
|
||||
* @param daysBefore number of days to roll back returned start date relative to current date
|
||||
* @param daysAfter number of days to roll forward returned end date relative to current date
|
||||
* @param parentNotBefore if provided is used to lower bound the date interval returned
|
||||
* @param parentNotAfter if provided is used to upper bound the date interval returned
|
||||
* Note we use Date rather than LocalDate as the consuming java.security and BouncyCastle certificate apis all use Date
|
||||
* Helper method to get a notBefore and notAfter pair from current day bounded by parent certificate validity range.
|
||||
* @param daysBefore number of days to roll back returned start date relative to current date.
|
||||
* @param daysAfter number of days to roll forward returned end date relative to current date.
|
||||
* @param parentNotBefore if provided is used to lower bound the date interval returned.
|
||||
* @param parentNotAfter if provided is used to upper bound the date interval returned.
|
||||
* Note we use Date rather than LocalDate as the consuming java.security and BouncyCastle certificate apis all use Date.
|
||||
* Thus we avoid too many round trip conversions.
|
||||
*/
|
||||
private fun getCertificateValidityWindow(daysBefore: Int, daysAfter: Int, parentNotBefore: Date? = null, parentNotAfter: Date? = null): Pair<Date, Date> {
|
||||
@ -96,57 +96,56 @@ object X509Utilities {
|
||||
|
||||
/*
|
||||
* Create a de novo root self-signed X509 v3 CA cert and [KeyPair].
|
||||
* @param subject the cert Subject will be populated with the domain string
|
||||
* @param subject the cert Subject will be populated with the domain string.
|
||||
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return A data class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates.
|
||||
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates
|
||||
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACert(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKey {
|
||||
fun createSelfSignedCACert(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
|
||||
val cert = Crypto.createCertificate(subject, keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 2)
|
||||
return CertificateAndKey(cert, keyPair)
|
||||
return CertificateAndKeyPair(cert, keyPair)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
|
||||
validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKey
|
||||
validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair
|
||||
= createSelfSignedCACert(subject, generateKeyPair(signatureScheme), validityWindow)
|
||||
|
||||
/**
|
||||
* Create a de novo root intermediate X509 v3 CA cert and KeyPair.
|
||||
* @param subject subject of the generated certificate.
|
||||
* @param ca The Public certificate and KeyPair of the root CA certificate above this used to sign it
|
||||
* @param ca The Public certificate and KeyPair of the root CA certificate above this used to sign it.
|
||||
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates.
|
||||
* Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates
|
||||
* Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createIntermediateCert(subject: X500Name, ca: CertificateAndKey, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKey {
|
||||
fun createIntermediateCert(subject: X500Name, ca: CertificateAndKeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
|
||||
val keyPair = generateKeyPair(signatureScheme)
|
||||
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate.notBefore, ca.certificate.notAfter)
|
||||
val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 1)
|
||||
return CertificateAndKey(cert, keyPair)
|
||||
return CertificateAndKeyPair(cert, keyPair)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an X509v3 certificate suitable for use in TLS roles.
|
||||
* @param subject The contents to put in the subject field of the certificate
|
||||
* @param publicKey The PublicKey to be wrapped in the certificate
|
||||
* @param ca The Public certificate and KeyPair of the parent CA that will sign this certificate
|
||||
* @param subjectAlternativeNameDomains A set of alternate DNS names to be supported by the certificate during validation of the TLS handshakes
|
||||
* @param subjectAlternativeNameIps A set of alternate IP addresses to be supported by the certificate during validation of the TLS handshakes
|
||||
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
|
||||
* @param subject The contents to put in the subject field of the certificate.
|
||||
* @param publicKey The PublicKey to be wrapped in the certificate.
|
||||
* @param ca The Public certificate and KeyPair of the parent CA that will sign this certificate.
|
||||
* @param subjectAlternativeNameDomains A set of alternate DNS names to be supported by the certificate during validation of the TLS handshakes.
|
||||
* @param subjectAlternativeNameIps A set of alternate IP addresses to be supported by the certificate during validation of the TLS handshakes.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return The generated X509Certificate suitable for use as a Server/Client certificate in TLS.
|
||||
* This certificate is not marked as a CA cert to be similar in nature to commercial certificates.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createServerCert(subject: X500Name, publicKey: PublicKey,
|
||||
ca: CertificateAndKey,
|
||||
ca: CertificateAndKeyPair,
|
||||
subjectAlternativeNameDomains: List<String>,
|
||||
subjectAlternativeNameIps: List<String>,
|
||||
validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
|
||||
@ -168,8 +167,8 @@ object X509Utilities {
|
||||
* @param targetCertAndKey certificate the path ends at.
|
||||
* @param revocationEnabled whether revocation of certificates in the path should be checked.
|
||||
*/
|
||||
fun createCertificatePath(rootCertAndKey: CertificateAndKey,
|
||||
targetCertAndKey: CertificateAndKey,
|
||||
fun createCertificatePath(rootCertAndKey: CertificateAndKeyPair,
|
||||
targetCertAndKey: CertificateAndKeyPair,
|
||||
revocationEnabled: Boolean): CertPathBuilderResult {
|
||||
val intermediateCertificates = setOf(targetCertAndKey.certificate)
|
||||
val certStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(intermediateCertificates))
|
||||
@ -189,9 +188,9 @@ object X509Utilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to store a .pem/.cer format file copy of a certificate if required for import into a PC/Mac, or for inspection
|
||||
* @param x509Certificate certificate to save
|
||||
* @param filename Target filename
|
||||
* Helper method to store a .pem/.cer format file copy of a certificate if required for import into a PC/Mac, or for inspection.
|
||||
* @param x509Certificate certificate to save.
|
||||
* @param filename Target filename.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, filename: Path) {
|
||||
@ -203,9 +202,9 @@ object X509Utilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to load back a .pem/.cer format file copy of a certificate
|
||||
* @param filename Source filename
|
||||
* @return The X509Certificate that was encoded in the file
|
||||
* Helper method to load back a .pem/.cer format file copy of a certificate.
|
||||
* @param filename Source filename.
|
||||
* @return The X509Certificate that was encoded in the file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun loadCertificateFromPEMFile(filename: Path): X509Certificate {
|
||||
@ -217,14 +216,14 @@ object X509Utilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine
|
||||
* @param keyStoreFilePath KeyStore path to save output to
|
||||
* @param storePassword access password for KeyStore
|
||||
* An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine.
|
||||
* @param keyStoreFilePath KeyStore path to save output to.
|
||||
* @param storePassword access password for KeyStore.
|
||||
* @param keyPassword PrivateKey access password for the generated keys.
|
||||
* It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same.
|
||||
* @param caKeyStore KeyStore containing CA keys generated by createCAKeyStoreAndTrustStore
|
||||
* @param caKeyPassword password to unlock private keys in the CA KeyStore
|
||||
* @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications
|
||||
* @param caKeyStore KeyStore containing CA keys generated by createCAKeyStoreAndTrustStore.
|
||||
* @param caKeyPassword password to unlock private keys in the CA KeyStore.
|
||||
* @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications.
|
||||
*/
|
||||
fun createKeystoreForSSL(keyStoreFilePath: Path,
|
||||
storePassword: String,
|
||||
@ -234,8 +233,8 @@ object X509Utilities {
|
||||
commonName: X500Name,
|
||||
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME): KeyStore {
|
||||
|
||||
val rootCA = caKeyStore.getCertificateAndKey(CORDA_ROOT_CA_PRIVATE_KEY, caKeyPassword)
|
||||
val intermediateCA = caKeyStore.getCertificateAndKey(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, caKeyPassword)
|
||||
val rootCA = caKeyStore.getCertificateAndKeyPair(CORDA_ROOT_CA_PRIVATE_KEY, caKeyPassword)
|
||||
val intermediateCA = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, caKeyPassword)
|
||||
|
||||
val serverKey = generateKeyPair(signatureScheme)
|
||||
val host = InetAddress.getLocalHost()
|
||||
@ -259,7 +258,7 @@ object X509Utilities {
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present, this throws an
|
||||
* exception
|
||||
* exception.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
|
||||
@ -269,7 +268,7 @@ fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName
|
||||
* adds one.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { attr -> commonName }
|
||||
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
|
||||
@ -307,4 +306,4 @@ class CertificateStream(val input: InputStream) {
|
||||
fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate
|
||||
}
|
||||
|
||||
data class CertificateAndKey(val certificate: X509Certificate, val keyPair: KeyPair)
|
||||
data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.core.identity
|
||||
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.crypto.CertificateAndKey
|
||||
import net.corda.core.crypto.CertificateAndKeyPair
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
@ -27,7 +27,7 @@ import java.security.PublicKey
|
||||
* @see CompositeKey
|
||||
*/
|
||||
class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) {
|
||||
constructor(certAndKey: CertificateAndKey) : this(X500Name(certAndKey.certificate.subjectDN.name), certAndKey.keyPair.public)
|
||||
constructor(certAndKey: CertificateAndKeyPair) : this(X500Name(certAndKey.certificate.subjectDN.name), certAndKey.keyPair.public)
|
||||
override fun toString() = name.toString()
|
||||
override fun nameOrNull(): X500Name? = name
|
||||
|
||||
|
@ -2,7 +2,6 @@ package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.div
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.corda.testing.getTestX509Name
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
@ -177,14 +176,14 @@ class X509UtilitiesTest {
|
||||
|
||||
// Load signing intermediate CA cert
|
||||
val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass")
|
||||
val caCertAndKey = caKeyStore.getCertificateAndKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "cakeypass")
|
||||
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "cakeypass")
|
||||
|
||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||
X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
|
||||
// Load back server certificate
|
||||
val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass")
|
||||
val serverCertAndKey = serverKeyStore.getCertificateAndKey(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY, "serverkeypass")
|
||||
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY, "serverkeypass")
|
||||
|
||||
serverCertAndKey.certificate.checkValidity(Date())
|
||||
serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey)
|
||||
@ -349,4 +348,18 @@ class X509UtilitiesTest {
|
||||
|
||||
return keyStore
|
||||
}
|
||||
@Test
|
||||
fun `Get correct private key type from Keystore`() {
|
||||
val keyPair = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), keyPair)
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
|
||||
val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
|
||||
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")
|
||||
|
||||
assertTrue(keyFromKeystore is java.security.interfaces.ECPrivateKey) // by default JKS returns SUN EC key
|
||||
assertTrue(keyFromKeystoreCasted is org.bouncycastle.jce.interfaces.ECPrivateKey)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -605,7 +605,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
|
||||
val identityAndKey = if (configuration.keyStoreFile.exists() && keystore.containsAlias(privateKeyAlias)) {
|
||||
// Get keys from keystore.
|
||||
val (cert, keyPair) = keystore.getCertificateAndKey(privateKeyAlias, configuration.keyStorePassword)
|
||||
val (cert, keyPair) = keystore.getCertificateAndKeyPair(privateKeyAlias, configuration.keyStorePassword)
|
||||
val loadedServiceName = X509CertificateHolder(cert.encoded).subject
|
||||
if (X509CertificateHolder(cert.encoded).subject != serviceName) {
|
||||
throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:" +
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.crypto.CertificateAndKey
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
|
Loading…
x
Reference in New Issue
Block a user