mirror of
https://github.com/corda/corda.git
synced 2025-05-28 05:04:22 +00:00
Restructure Crypto to use ASN.1 algorithm identifiers
Remove use of Sun internal APIs and algorithm identifiers (which are incomplete and non-standard) in Crypto. Also eliminates uncertainty about which signature scheme is being used (and therefore iterating through several to find the correct one).
This commit is contained in:
parent
5a45459b9d
commit
083b8265b5
@ -14,7 +14,7 @@ import java.security.Signature
|
||||
*/
|
||||
object ContentSignerBuilder {
|
||||
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider?, random: SecureRandom? = null): ContentSigner {
|
||||
val sigAlgId = AlgorithmIdentifier(signatureScheme.signatureOID)
|
||||
val sigAlgId = signatureScheme.signatureOID
|
||||
val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply {
|
||||
if (random != null) {
|
||||
initSign(privateKey, random)
|
||||
|
@ -1,24 +1,23 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.random63BitValue
|
||||
import net.i2p.crypto.eddsa.*
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import net.i2p.crypto.eddsa.EdDSASecurityProvider
|
||||
import net.i2p.crypto.eddsa.math.GroupElement
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import org.bouncycastle.asn1.ASN1Sequence
|
||||
import org.bouncycastle.asn1.DERSequence
|
||||
import org.bouncycastle.asn1.*
|
||||
import org.bouncycastle.asn1.bc.BCObjectIdentifiers
|
||||
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
||||
import org.bouncycastle.asn1.sec.SECObjectIdentifiers
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
@ -45,13 +44,8 @@ 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
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
@ -80,7 +74,8 @@ object Crypto {
|
||||
val RSA_SHA256 = SignatureScheme(
|
||||
1,
|
||||
"RSA_SHA256",
|
||||
PKCSObjectIdentifiers.id_RSASSA_PSS,
|
||||
AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null),
|
||||
emptyList(),
|
||||
BouncyCastleProvider.PROVIDER_NAME,
|
||||
"RSA",
|
||||
"SHA256WITHRSAANDMGF1",
|
||||
@ -93,7 +88,8 @@ object Crypto {
|
||||
val ECDSA_SECP256K1_SHA256 = SignatureScheme(
|
||||
2,
|
||||
"ECDSA_SECP256K1_SHA256",
|
||||
X9ObjectIdentifiers.ecdsa_with_SHA256,
|
||||
AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256k1),
|
||||
listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256k1)),
|
||||
BouncyCastleProvider.PROVIDER_NAME,
|
||||
"ECDSA",
|
||||
"SHA256withECDSA",
|
||||
@ -106,7 +102,8 @@ object Crypto {
|
||||
val ECDSA_SECP256R1_SHA256 = SignatureScheme(
|
||||
3,
|
||||
"ECDSA_SECP256R1_SHA256",
|
||||
X9ObjectIdentifiers.ecdsa_with_SHA256,
|
||||
AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256r1),
|
||||
listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256r1)),
|
||||
BouncyCastleProvider.PROVIDER_NAME,
|
||||
"ECDSA",
|
||||
"SHA256withECDSA",
|
||||
@ -119,10 +116,12 @@ object Crypto {
|
||||
val EDDSA_ED25519_SHA512 = SignatureScheme(
|
||||
4,
|
||||
"EDDSA_ED25519_SHA512",
|
||||
ASN1ObjectIdentifier("1.3.101.112"),
|
||||
// OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00
|
||||
AlgorithmIdentifier(ASN1ObjectIdentifier("1.3.101.112"), null),
|
||||
emptyList(),
|
||||
// We added EdDSA to bouncy castle for certificate signing.
|
||||
BouncyCastleProvider.PROVIDER_NAME,
|
||||
EdDSAKey.KEY_ALGORITHM,
|
||||
"1.3.101.112",
|
||||
EdDSAEngine.SIGNATURE_ALGORITHM,
|
||||
EdDSANamedCurveTable.getByName("ED25519"),
|
||||
256,
|
||||
@ -133,10 +132,12 @@ object Crypto {
|
||||
* SPHINCS-256 hash-based signature scheme. It provides 128bit security against post-quantum attackers
|
||||
* at the cost of larger key sizes and loss of compatibility.
|
||||
*/
|
||||
val SHA512_256 = DLSequence(arrayOf(NISTObjectIdentifiers.id_sha512_256))
|
||||
val SPHINCS256_SHA256 = SignatureScheme(
|
||||
5,
|
||||
"SPHINCS-256_SHA512",
|
||||
BCObjectIdentifiers.sphincs256_with_SHA512,
|
||||
AlgorithmIdentifier(BCObjectIdentifiers.sphincs256_with_SHA512, DLSequence(arrayOf(ASN1Integer(0), SHA512_256))),
|
||||
listOf(AlgorithmIdentifier(BCObjectIdentifiers.sphincs256, DLSequence(arrayOf(ASN1Integer(0), SHA512_256)))),
|
||||
"BCPQC",
|
||||
"SPHINCS256",
|
||||
"SHA512WITHSPHINCS256",
|
||||
@ -161,9 +162,14 @@ 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 }
|
||||
/**
|
||||
* Map of X.509 algorithm identifiers to signature schemes Corda recognises. See RFC 2459 for the format of
|
||||
* algorithm identifiers.
|
||||
*/
|
||||
private val algorithmMap: Map<AlgorithmIdentifier, SignatureScheme>
|
||||
= (supportedSignatureSchemes.values.flatMap { scheme -> scheme.alternativeOIDs.map { oid -> Pair(oid, scheme) } }
|
||||
+ supportedSignatureSchemes.values.map { Pair(it.signatureOID, it) })
|
||||
.toMap()
|
||||
|
||||
// This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider
|
||||
// that could cause unexpected and suspicious behaviour.
|
||||
@ -175,7 +181,7 @@ object Crypto {
|
||||
|
||||
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
|
||||
putAll(EdDSASecurityProvider())
|
||||
addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID, KeyInfoConverter(EDDSA_ED25519_SHA512))
|
||||
addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID.algorithm, KeyInfoConverter(EDDSA_ED25519_SHA512))
|
||||
}
|
||||
|
||||
init {
|
||||
@ -184,6 +190,21 @@ object Crypto {
|
||||
Security.addProvider(getBouncyCastleProvider())
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise an algorithm identifier by converting [DERNull] parameters into a Kotlin null value.
|
||||
*/
|
||||
private fun normaliseAlgorithmIdentifier(id: AlgorithmIdentifier): AlgorithmIdentifier {
|
||||
return if (id.parameters is DERNull) {
|
||||
AlgorithmIdentifier(id.algorithm, null)
|
||||
} else {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
fun findSignatureScheme(algorithm: AlgorithmIdentifier): SignatureScheme {
|
||||
return algorithmMap[normaliseAlgorithmIdentifier(algorithm)] ?: throw IllegalArgumentException("Unrecognised algorithm: ${algorithm}")
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory pattern to retrieve the corresponding [SignatureScheme] based on the type of the [String] input.
|
||||
* This function is usually called by key generators and verify signature functions.
|
||||
@ -192,6 +213,7 @@ object Crypto {
|
||||
* @return a currently supported SignatureScheme.
|
||||
* @throws IllegalArgumentException if the requested signature scheme is not supported.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun findSignatureScheme(schemeCodeName: String): SignatureScheme = supportedSignatureSchemes[schemeCodeName] ?: throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $schemeCodeName")
|
||||
|
||||
/**
|
||||
@ -202,10 +224,24 @@ object Crypto {
|
||||
* @return a currently supported SignatureScheme.
|
||||
* @throws IllegalArgumentException if the requested key type is not supported.
|
||||
*/
|
||||
fun findSignatureScheme(key: Key): SignatureScheme {
|
||||
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")
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun findSignatureScheme(key: PublicKey): SignatureScheme {
|
||||
val keyInfo = SubjectPublicKeyInfo.getInstance(key.encoded)
|
||||
return findSignatureScheme(keyInfo.algorithm)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun findSignatureScheme(key: PrivateKey): SignatureScheme {
|
||||
val keyInfo = PrivateKeyInfo.getInstance(key.encoded)
|
||||
return findSignatureScheme(keyInfo.privateKeyAlgorithm)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -217,19 +253,9 @@ object Crypto {
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
|
||||
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(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
|
||||
} catch (ikse: InvalidKeySpecException) {
|
||||
// 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.")
|
||||
val keyInfo = PrivateKeyInfo.getInstance(encodedKey)
|
||||
val signatureScheme = findSignatureScheme(keyInfo.privateKeyAlgorithm)
|
||||
return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,19 +296,9 @@ object Crypto {
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun decodePublicKey(encodedKey: ByteArray): PublicKey {
|
||||
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(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
|
||||
} catch (ikse: InvalidKeySpecException) {
|
||||
// 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.")
|
||||
val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(encodedKey)
|
||||
val signatureScheme = findSignatureScheme(subjectPublicKeyInfo.algorithm)
|
||||
return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -834,16 +850,6 @@ 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) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||
import java.security.Signature
|
||||
import java.security.spec.AlgorithmParameterSpec
|
||||
|
||||
@ -8,7 +8,9 @@ import java.security.spec.AlgorithmParameterSpec
|
||||
* This class is used to define a digital signature scheme.
|
||||
* @param schemeNumberID we assign a number ID for more efficient on-wire serialisation. Please ensure uniqueness between schemes.
|
||||
* @param schemeCodeName code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, EDDSA_ED25519_SHA512, SPHINCS-256_SHA512).
|
||||
* @param signatureOID object identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA)
|
||||
* @param signatureOID ASN.1 algorithm identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA)
|
||||
* @param alternativeOIDs ASN.1 algorithm identifiers for keys of the signature, where we want to map multiple keys to
|
||||
* the same signature scheme.
|
||||
* @param providerName the provider's name (e.g. "BC").
|
||||
* @param algorithmName which signature algorithm is used (e.g. RSA, ECDSA. EdDSA, SPHINCS-256).
|
||||
* @param signatureName a signature-scheme name as required to create [Signature] objects (e.g. "SHA256withECDSA")
|
||||
@ -20,7 +22,8 @@ import java.security.spec.AlgorithmParameterSpec
|
||||
data class SignatureScheme(
|
||||
val schemeNumberID: Int,
|
||||
val schemeCodeName: String,
|
||||
val signatureOID: ASN1ObjectIdentifier,
|
||||
val signatureOID: AlgorithmIdentifier,
|
||||
val alternativeOIDs: List<AlgorithmIdentifier>,
|
||||
val providerName: String,
|
||||
val algorithmName: String,
|
||||
val signatureName: String,
|
||||
|
Loading…
x
Reference in New Issue
Block a user