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:
Ross Nicoll 2017-06-29 14:36:39 +01:00 committed by GitHub
parent 5a45459b9d
commit 083b8265b5
3 changed files with 77 additions and 68 deletions

View File

@ -14,7 +14,7 @@ import java.security.Signature
*/ */
object ContentSignerBuilder { object ContentSignerBuilder {
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider?, random: SecureRandom? = null): ContentSigner { 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 { val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply {
if (random != null) { if (random != null) {
initSign(privateKey, random) initSign(privateKey, random)

View File

@ -1,24 +1,23 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.random63BitValue 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.math.GroupElement
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.bouncycastle.asn1.ASN1EncodableVector import org.bouncycastle.asn1.*
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.bc.BCObjectIdentifiers import org.bouncycastle.asn1.bc.BCObjectIdentifiers
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.sec.SECObjectIdentifiers
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.BasicConstraints import org.bouncycastle.asn1.x509.*
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder 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.BCSphincs256PrivateKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec 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.math.BigInteger
import java.security.* import java.security.*
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.spec.InvalidKeySpecException import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
@ -80,7 +74,8 @@ object Crypto {
val RSA_SHA256 = SignatureScheme( val RSA_SHA256 = SignatureScheme(
1, 1,
"RSA_SHA256", "RSA_SHA256",
PKCSObjectIdentifiers.id_RSASSA_PSS, AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null),
emptyList(),
BouncyCastleProvider.PROVIDER_NAME, BouncyCastleProvider.PROVIDER_NAME,
"RSA", "RSA",
"SHA256WITHRSAANDMGF1", "SHA256WITHRSAANDMGF1",
@ -93,7 +88,8 @@ object Crypto {
val ECDSA_SECP256K1_SHA256 = SignatureScheme( val ECDSA_SECP256K1_SHA256 = SignatureScheme(
2, 2,
"ECDSA_SECP256K1_SHA256", "ECDSA_SECP256K1_SHA256",
X9ObjectIdentifiers.ecdsa_with_SHA256, AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256k1),
listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256k1)),
BouncyCastleProvider.PROVIDER_NAME, BouncyCastleProvider.PROVIDER_NAME,
"ECDSA", "ECDSA",
"SHA256withECDSA", "SHA256withECDSA",
@ -106,7 +102,8 @@ object Crypto {
val ECDSA_SECP256R1_SHA256 = SignatureScheme( val ECDSA_SECP256R1_SHA256 = SignatureScheme(
3, 3,
"ECDSA_SECP256R1_SHA256", "ECDSA_SECP256R1_SHA256",
X9ObjectIdentifiers.ecdsa_with_SHA256, AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256r1),
listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256r1)),
BouncyCastleProvider.PROVIDER_NAME, BouncyCastleProvider.PROVIDER_NAME,
"ECDSA", "ECDSA",
"SHA256withECDSA", "SHA256withECDSA",
@ -119,10 +116,12 @@ object Crypto {
val EDDSA_ED25519_SHA512 = SignatureScheme( val EDDSA_ED25519_SHA512 = SignatureScheme(
4, 4,
"EDDSA_ED25519_SHA512", "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. // We added EdDSA to bouncy castle for certificate signing.
BouncyCastleProvider.PROVIDER_NAME, BouncyCastleProvider.PROVIDER_NAME,
EdDSAKey.KEY_ALGORITHM, "1.3.101.112",
EdDSAEngine.SIGNATURE_ALGORITHM, EdDSAEngine.SIGNATURE_ALGORITHM,
EdDSANamedCurveTable.getByName("ED25519"), EdDSANamedCurveTable.getByName("ED25519"),
256, 256,
@ -133,10 +132,12 @@ object Crypto {
* SPHINCS-256 hash-based signature scheme. It provides 128bit security against post-quantum attackers * 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. * at the cost of larger key sizes and loss of compatibility.
*/ */
val SHA512_256 = DLSequence(arrayOf(NISTObjectIdentifiers.id_sha512_256))
val SPHINCS256_SHA256 = SignatureScheme( val SPHINCS256_SHA256 = SignatureScheme(
5, 5,
"SPHINCS-256_SHA512", "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", "BCPQC",
"SPHINCS256", "SPHINCS256",
"SHA512WITHSPHINCS256", "SHA512WITHSPHINCS256",
@ -161,9 +162,14 @@ object Crypto {
SPHINCS256_SHA256 SPHINCS256_SHA256
).associateBy { it.schemeCodeName } ).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. * Map of X.509 algorithm identifiers to signature schemes Corda recognises. See RFC 2459 for the format of
private val algorithmGroups = supportedSignatureSchemes.values.groupBy { it.algorithmName } * 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 // This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider
// that could cause unexpected and suspicious behaviour. // that could cause unexpected and suspicious behaviour.
@ -175,7 +181,7 @@ object Crypto {
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply { private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
putAll(EdDSASecurityProvider()) putAll(EdDSASecurityProvider())
addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID, KeyInfoConverter(EDDSA_ED25519_SHA512)) addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID.algorithm, KeyInfoConverter(EDDSA_ED25519_SHA512))
} }
init { init {
@ -184,6 +190,21 @@ object Crypto {
Security.addProvider(getBouncyCastleProvider()) 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. * 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. * This function is usually called by key generators and verify signature functions.
@ -192,6 +213,7 @@ object Crypto {
* @return a currently supported SignatureScheme. * @return a currently supported SignatureScheme.
* @throws IllegalArgumentException if the requested signature scheme is not supported. * @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") 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. * @return a currently supported SignatureScheme.
* @throws IllegalArgumentException if the requested key type is not supported. * @throws IllegalArgumentException if the requested key type is not supported.
*/ */
fun findSignatureScheme(key: Key): SignatureScheme { @Throws(IllegalArgumentException::class)
val algorithm = matchingAlgorithmName(key.algorithm) fun findSignatureScheme(key: PublicKey): SignatureScheme {
algorithmGroups[algorithm]?.filter { validateKey(it, key) }?.firstOrNull { return it } val keyInfo = SubjectPublicKeyInfo.getInstance(key.encoded)
throw IllegalArgumentException("Unsupported key algorithm: ${key.algorithm} or invalid key format") 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) @Throws(IllegalArgumentException::class)
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey { fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
val algorithm = matchingAlgorithmName(PKCS8Key.parseKey(DerValue(encodedKey)).algorithm) val keyInfo = PrivateKeyInfo.getInstance(encodedKey)
// There are cases where the same key algorithm is applied to different signature schemes. val signatureScheme = findSignatureScheme(keyInfo.privateKeyAlgorithm)
// Currently, this occurs with ECDSA as it applies to either secp256K1 or secp256R1 curves. return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
// 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.")
} }
/** /**
@ -270,19 +296,9 @@ object Crypto {
*/ */
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun decodePublicKey(encodedKey: ByteArray): PublicKey { fun decodePublicKey(encodedKey: ByteArray): PublicKey {
val algorithm = matchingAlgorithmName(X509Key.parse(DerValue(encodedKey)).algorithm) val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(encodedKey)
// There are cases where the same key algorithm is applied to different signature schemes. val signatureScheme = findSignatureScheme(subjectPublicKeyInfo.algorithm)
// Currently, this occurs with ECDSA as it applies to either secp256K1 or secp256R1 curves. return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
// 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.")
} }
/** /**
@ -834,16 +850,6 @@ object Crypto {
/** Check if the requested [SignatureScheme] is supported by the system. */ /** Check if the requested [SignatureScheme] is supported by the system. */
fun isSupportedSignatureScheme(signatureScheme: SignatureScheme): Boolean = supportedSignatureSchemes[signatureScheme.schemeCodeName] === signatureScheme 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. // validate a key, by checking its algorithmic params.
private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean { private fun validateKey(signatureScheme: SignatureScheme, key: Key): Boolean {
return when (key) { return when (key) {

View File

@ -1,6 +1,6 @@
package net.corda.core.crypto package net.corda.core.crypto
import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import java.security.Signature import java.security.Signature
import java.security.spec.AlgorithmParameterSpec import java.security.spec.AlgorithmParameterSpec
@ -8,7 +8,9 @@ import java.security.spec.AlgorithmParameterSpec
* This class is used to define a digital signature scheme. * 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 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 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 providerName the provider's name (e.g. "BC").
* @param algorithmName which signature algorithm is used (e.g. RSA, ECDSA. EdDSA, SPHINCS-256). * @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") * @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( data class SignatureScheme(
val schemeNumberID: Int, val schemeNumberID: Int,
val schemeCodeName: String, val schemeCodeName: String,
val signatureOID: ASN1ObjectIdentifier, val signatureOID: AlgorithmIdentifier,
val alternativeOIDs: List<AlgorithmIdentifier>,
val providerName: String, val providerName: String,
val algorithmName: String, val algorithmName: String,
val signatureName: String, val signatureName: String,