Allowing multiple signature algorithms (#250)

Basic crypto API to support 5 signature schemes and MetaData-ed signatures.
Supported schemes: (1) RSA_SHA256, (2) ECDSA_SECP256K1_SHA256, (3) ECDSA_SECP256R1_SHA256, (4) EDDSA_ED25519_SHA512, (5) SPHINCS-256_SHA512.

To sign a transaction, a signer should create a MetaData wrapper that contains transaction's merkle root and some extra information, such as signer's public key, timestamp and visibleInputs. Actually, MetaData is utilised to support a practical partial, blind and extra-data attached signature model.
When a MetaData object is signed, the signer sends a TransactionSignature object that contains the signed output and the corresponding MetaData object.

Remarks: 
This is an temporary solution for signature algorithmic agility. Further development is required for a robust and extensible Crypto Manager/Provider PKI that will support certificate creation, key generation, signing/verifying, deterministic key derivation, encoding formats, SGX/HSM support, identity and key management, versioning, revocation, asynchronicity, metadata, partial sig. policies etc.
This commit is contained in:
Konstantinos Chalkias 2017-03-08 17:45:23 +00:00 committed by GitHub
parent dc8c6747d3
commit 4a9ff84fc7
14 changed files with 1499 additions and 5 deletions

View File

@ -0,0 +1,421 @@
package net.corda.core.crypto
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAKey
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
/**
* This object controls and provides the available and supported signature schemes for Corda.
* Any implemented [SignatureScheme] should be strictly defined here.
* However, only the schemes returned by {@link #listSupportedSignatureSchemes()} are supported.
* Note that Corda currently supports the following signature schemes by their code names:
* <p><ul>
* <li>RSA_SHA256 (RSA using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function).
* <li>ECDSA_SECP256K1_SHA256 (ECDSA using the secp256k1 Koblitz curve and SHA256 as hash algorithm).
* <li>ECDSA_SECP256R1_SHA256 (ECDSA using the secp256r1 (NIST P-256) curve and SHA256 as hash algorithm).
* <li>EDDSA_ED25519_SHA512 (EdDSA using the ed255519 twisted Edwards curve and SHA512 as hash algorithm).
* <li>SPHINCS256_SHA512 (SPHINCS-256 hash-based signature scheme using SHA512 as hash algorithm).
* </ul>
*/
object Crypto {
/**
* RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function.
* Note: Recommended key size >= 3072 bits.
*/
private val RSA_SHA256 = SignatureScheme(
1,
"RSA_SHA256",
"RSA",
Signature.getInstance("SHA256WITHRSAANDMGF1", "BC"),
KeyFactory.getInstance("RSA", "BC"),
KeyPairGenerator.getInstance("RSA", "BC"),
null,
3072,
"RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function."
)
/** ECDSA signature scheme using the secp256k1 Koblitz curve. */
private val ECDSA_SECP256K1_SHA256 = SignatureScheme(
2,
"ECDSA_SECP256K1_SHA256",
"ECDSA",
Signature.getInstance("SHA256withECDSA", "BC"),
KeyFactory.getInstance("ECDSA", "BC"),
KeyPairGenerator.getInstance("ECDSA", "BC"),
ECNamedCurveTable.getParameterSpec("secp256k1"),
256,
"ECDSA signature scheme using the secp256k1 Koblitz curve."
)
/** ECDSA signature scheme using the secp256r1 (NIST P-256) curve. */
private val ECDSA_SECP256R1_SHA256 = SignatureScheme(
3,
"ECDSA_SECP256R1_SHA256",
"ECDSA",
Signature.getInstance("SHA256withECDSA", "BC"),
KeyFactory.getInstance("ECDSA", "BC"),
KeyPairGenerator.getInstance("ECDSA", "BC"),
ECNamedCurveTable.getParameterSpec("secp256r1"),
256,
"ECDSA signature scheme using the secp256r1 (NIST P-256) curve."
)
/** EdDSA signature scheme using the ed255519 twisted Edwards curve. */
private val EDDSA_ED25519_SHA512 = SignatureScheme(
4,
"EDDSA_ED25519_SHA512",
"EdDSA",
EdDSAEngine(),
EdDSAKeyFactory(),
net.i2p.crypto.eddsa.KeyPairGenerator(), // EdDSA engine uses a custom KeyPairGenerator Vs BouncyCastle.
EdDSANamedCurveTable.getByName("ed25519-sha-512"),
256,
"EdDSA signature scheme using the ed25519 twisted Edwards curve."
)
/**
* 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.
*/
private val SPHINCS256_SHA256 = SignatureScheme(
5,
"SPHINCS-256_SHA512",
"SPHINCS-256",
Signature.getInstance("SHA512WITHSPHINCS256", "BCPQC"),
KeyFactory.getInstance("SPHINCS256", "BCPQC"),
KeyPairGenerator.getInstance("SPHINCS256", "BCPQC"),
SPHINCS256KeyGenParameterSpec(SPHINCS256KeyGenParameterSpec.SHA512_256),
256,
"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."
)
/** Our default signature scheme if no algorithm is specified (e.g. for key generation). */
private val DEFAULT_SIGNATURE_SCHEME = EDDSA_ED25519_SHA512
/**
* Supported digital signature schemes.
* Note: Only the schemes added in this map will be supported (see [Crypto]).
* Do not forget to add the DEFAULT_SIGNATURE_SCHEME as well.
*/
private val supportedSignatureSchemes = mapOf(
RSA_SHA256.schemeCodeName to RSA_SHA256,
ECDSA_SECP256K1_SHA256.schemeCodeName to ECDSA_SECP256K1_SHA256,
ECDSA_SECP256R1_SHA256.schemeCodeName to ECDSA_SECP256R1_SHA256,
EDDSA_ED25519_SHA512.schemeCodeName to EDDSA_ED25519_SHA512,
SPHINCS256_SHA256.schemeCodeName to SPHINCS256_SHA256
)
/**
* 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.
* In case the input is not a key in the supportedSignatureSchemes map, null will be returned.
* @param schemeCodeName a [String] that should match a supported signature scheme code name (e.g. ECDSA_SECP256K1_SHA256), see [Crypto].
* @return a currently supported SignatureScheme.
* @throws IllegalArgumentException if the requested signature scheme is not supported.
*/
private fun findSignatureScheme(schemeCodeName: String): SignatureScheme = supportedSignatureSchemes[schemeCodeName] ?: throw IllegalArgumentException("Unsupported key/algorithm for metadata schemeCodeName: ${schemeCodeName}")
/**
* Retrieve the corresponding [SignatureScheme] based on the type of the input [KeyPair].
* Note that only the Corda platform standard schemes are supported (see [Crypto]).
* This function is usually called when requiring to sign signatures.
* @param keyPair a cryptographic [KeyPair].
* @return a currently supported SignatureScheme or null.
* @throws IllegalArgumentException if the requested signature scheme is not supported.
*/
private fun findSignatureScheme(keyPair: KeyPair): SignatureScheme = findSignatureScheme(keyPair.private)
/**
* 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).
* @param key either private or public.
* @return a currently supported SignatureScheme.
* @throws IllegalArgumentException if the requested key type is not supported.
*/
private fun findSignatureScheme(key: Key): SignatureScheme {
for (sig in supportedSignatureSchemes.values) {
val algorithm = key.algorithm
if (algorithm == sig.algorithmName) {
// If more than one ECDSA schemes are supported, we should distinguish between them by checking their curve parameters.
// TODO: change 'continue' to 'break' if only one EdDSA curve will be used.
if (algorithm == "EdDSA") {
if ((key as EdDSAKey).params == sig.algSpec) {
return sig
} else continue
} else if (algorithm == "ECDSA") {
if ((key as ECKey).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 private key: ${key.encoded.toBase58()}")
}
/**
* Retrieve the corresponding signature scheme code name based on the type of the input [Key].
* See [Crypto] for the supported scheme code names.
* @param key either private or public.
* @return signatureSchemeCodeName for a [Key].
* @throws IllegalArgumentException if the requested key type is not supported.
*/
fun findSignatureSchemeCodeName(key: Key): String = findSignatureScheme(key).schemeCodeName
/**
* Decode a PKCS8 encoded key to its [PrivateKey] object.
* @param encodedKey a PKCS8 encoded private key.
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for this key factory to produce a private key.
*/
@Throws(IllegalArgumentException::class)
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
for (sig in supportedSignatureSchemes.values) {
try {
return sig.keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
// ignore it - only used to bypass the scheme that causes an exception.
}
}
throw IllegalArgumentException("This private key cannot be decoded, please ensure it is PKCS8 encoded and the signature scheme is supported.")
}
/**
* Decode a PKCS8 encoded key to its [PrivateKey] object based on the input scheme code name.
* This will be used by Kryo deserialisation.
* @param encodedKey a PKCS8 encoded private key.
* @param schemeCodeName a [String] that should match a key in supportedSignatureSchemes map (e.g. ECDSA_SECP256K1_SHA256).
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for this key factory to produce a private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
fun decodePrivateKey(encodedKey: ByteArray, schemeCodeName: String): PrivateKey {
val sig = findSignatureScheme(schemeCodeName)
try {
return sig.keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
throw InvalidKeySpecException("This private key cannot be decoded, please ensure it is PKCS8 encoded and that it corresponds to the input scheme's code name.", ikse)
}
}
/**
* Decode an X509 encoded key to its [PublicKey] object.
* @param encodedKey an X509 encoded public key.
* @throws UnsupportedSchemeException on not supported scheme.
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for this key factory to produce a private key.
*/
@Throws(IllegalArgumentException::class)
fun decodePublicKey(encodedKey: ByteArray): PublicKey {
for (sig in supportedSignatureSchemes.values) {
try {
return sig.keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
// ignore it - only used to bypass the scheme that causes an exception.
}
}
throw IllegalArgumentException("This public key cannot be decoded, please ensure it is X509 encoded and the signature scheme is supported.")
}
/**
* Decode an X509 encoded key to its [PrivateKey] object based on the input scheme code name.
* This will be used by Kryo deserialisation.
* @param encodedKey an X509 encoded public key.
* @param schemeCodeName a [String] that should match a key in supportedSignatureSchemes map (e.g. ECDSA_SECP256K1_SHA256).
* @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.
*/
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
fun decodePublicKey(encodedKey: ByteArray, schemeCodeName: String): PublicKey {
val sig = findSignatureScheme(schemeCodeName)
try {
return sig.keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
throw throw InvalidKeySpecException("This public key cannot be decoded, please ensure it is X509 encoded and that it corresponds to the input scheme's code name.", ikse)
}
}
/**
* Utility to simplify the act of generating keys.
* Normally, we don't expect other errors here, assuming that key generation parameters for every supported signature scheme have been unit-tested.
* @param schemeCodeName a signature scheme's code name (e.g. ECDSA_SECP256K1_SHA256).
* @return a KeyPair for the requested scheme.
* @throws IllegalArgumentException if the requested signature scheme is not supported.
*/
@Throws(IllegalArgumentException::class)
fun generateKeyPair(schemeCodeName: String): KeyPair = findSignatureScheme(schemeCodeName).keyPairGenerator.generateKeyPair()
/**
* Generate a KeyPair using the default signature scheme.
* @return a new KeyPair.
*/
fun generateKeyPair(): KeyPair = DEFAULT_SIGNATURE_SCHEME.keyPairGenerator.generateKeyPair()
/**
* Generic way to sign [ByteArray] data with a [PrivateKey]. Strategy on on identifying the actual signing scheme is based
* on the [PrivateKey] type, but if the schemeCodeName is known, then better use doSign(signatureScheme: String, privateKey: PrivateKey, clearData: ByteArray).
* @param privateKey the signer's [PrivateKey].
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun doSign(privateKey: PrivateKey, clearData: ByteArray) = doSign(findSignatureScheme(privateKey).sig, privateKey, clearData)
/**
* Generic way to sign [ByteArray] data with a [PrivateKey] and a known schemeCodeName [String].
* @param schemeCodeName a signature scheme's code name (e.g. ECDSA_SECP256K1_SHA256).
* @param privateKey the signer's [PrivateKey].
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun doSign(schemeCodeName: String, privateKey: PrivateKey, clearData: ByteArray) = doSign(findSignatureScheme(schemeCodeName).sig, privateKey, clearData)
/**
* Generic way to sign [ByteArray] data with a [PrivateKey] and a known [Signature].
* @param signature a [Signature] object, retrieved from supported signature schemes, see [Crypto].
* @param privateKey the signer's [PrivateKey].
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
private fun doSign(signature: Signature, privateKey: PrivateKey, clearData: ByteArray): ByteArray {
if (clearData.isEmpty()) throw Exception("Signing of an empty array is not permitted!")
signature.initSign(privateKey)
signature.update(clearData)
return signature.sign()
}
/**
* Generic way to sign [MetaData] objects with a [PrivateKey].
* [MetaData] is a wrapper over the transaction's Merkle root in order to attach extra information, such as a timestamp or partial and blind signature indicators.
* @param privateKey the signer's [PrivateKey].
* @param metaData a [MetaData] object that adds extra information to a transaction.
* @return a [TransactionSignature] object than contains the output of a successful signing and the metaData.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or
* if metaData.schemeCodeName is not aligned with key type.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun doSign(privateKey: PrivateKey, metaData: MetaData): TransactionSignature {
val sigKey: SignatureScheme = findSignatureScheme(privateKey)
val sigMetaData: SignatureScheme = findSignatureScheme(metaData.schemeCodeName)
if (sigKey != sigMetaData) throw IllegalArgumentException("Metadata schemeCodeName: ${metaData.schemeCodeName} is not aligned with the key type.")
val signatureData = doSign(sigKey.schemeCodeName, privateKey, metaData.bytes())
return TransactionSignature(signatureData, metaData)
}
/**
* Utility to simplify the act of verifying a digital signature.
* It returns true if it succeeds, but it always throws an exception if verification fails.
* @param publicKey the signer's [PublicKey].
* @param signatureData the signatureData on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @return true if verification passes or throws an exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray) = doVerify(findSignatureScheme(schemeCodeName).sig, publicKey, signatureData, clearData)
/**
* Utility to simplify the act of verifying a digital signature by identifying the signature scheme used from the input public key's type.
* It returns true if it succeeds, but it always throws an exception if verification fails.
* Strategy on identifying the actual signing scheme is based on the [PublicKey] type, but if the schemeCodeName is known,
* then better use doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray).
* @param publicKey the signer's [PublicKey].
* @param signatureData the signatureData on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @return true if verification passes or throws an exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun doVerify(publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray) = doVerify(findSignatureScheme(publicKey).sig, publicKey, signatureData, clearData)
/**
* Method to verify a digital signature.
* It returns true if it succeeds, but it always throws an exception if verification fails.
* @param signature a [Signature] object, retrieved from supported signature schemes, see [Crypto].
* @param publicKey the signer's [PublicKey].
* @param signatureData the signatureData on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @return true if verification passes or throws an exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if any of the clear or signature data is empty.
*/
private fun doVerify(signature: Signature, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean {
if (signatureData.isEmpty()) throw IllegalArgumentException("Signature data is empty!")
if (clearData.isEmpty()) throw IllegalArgumentException("Clear data is empty, nothing to verify!")
signature.initVerify(publicKey)
signature.update(clearData)
val verificationResult = signature.verify(signatureData)
if (verificationResult) {
return true
} else {
throw SignatureException("Signature Verification failed!")
}
}
/**
* Utility to simplify the act of verifying a [TransactionSignature].
* It returns true if it succeeds, but it always throws an exception if verification fails.
* @param publicKey the signer's [PublicKey].
* @param transactionSignature the signatureData on a message.
* @return true if verification passes or throws an exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/
@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()}")
return Crypto.doVerify(publicKey, transactionSignature.signatureData, transactionSignature.metaData.bytes())
}
/**
* Check if the requested signature scheme is supported by the system.
* @param schemeCodeName a signature scheme's code name (e.g. ECDSA_SECP256K1_SHA256).
* @return true if the signature scheme is supported.
*/
fun isSupportedSignatureScheme(schemeCodeName: String): Boolean = schemeCodeName in supportedSignatureSchemes
/** @return the default signature scheme's code name. */
fun getDefaultSignatureSchemeCodeName(): String = DEFAULT_SIGNATURE_SCHEME.schemeCodeName
/** @return a [List] of Strings with the scheme code names defined in [SignatureScheme] for all of our supported signature schemes, see [Crypto]. */
fun listSupportedSignatureSchemes(): List<String> = supportedSignatureSchemes.keys.toList()
}

View File

@ -0,0 +1,117 @@
package net.corda.core.crypto
import java.security.*
/**
* Helper function for signing.
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun PrivateKey.sign(clearData: ByteArray): ByteArray = Crypto.doSign(this, clearData)
/**
* Helper function for signing.
* @param metaDataFull tha attached MetaData object.
* @return a [DSWithMetaDataFull] object.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PrivateKey.sign(metaData: MetaData): TransactionSignature = Crypto.doSign(this, metaData)
/**
* Helper function to sign with a key pair.
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(clearData: ByteArray): ByteArray = Crypto.doSign(this.private, clearData)
/**
* Helper function to verify a signature.
* @param signatureData the signature on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this, signatureData, clearData)
/**
* Helper function to verify a metadata attached signature. It is noted that the transactionSignature contains
* signatureData and a [MetaData] object that contains the signer's public key and the transaction's Merkle root.
* @param transactionSignature a [TransactionSignature] object that .
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PublicKey.verify(transactionSignature: TransactionSignature): Boolean {
return Crypto.doVerify(this, transactionSignature)
}
/**
* Helper function for the signers to verify their own signature.
* @param signature the signature on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this.public, signatureData, clearData)
/**
* Generate a securely random [ByteArray] of requested number of bytes. Usually used for seeds, nonces and keys.
* @param numOfBytes how many random bytes to output.
* @return a random [ByteArray].
* @throws NoSuchAlgorithmException thrown if "NativePRNGNonBlocking" is not supported on the JVM
* or if no strong SecureRandom implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
* which should never happen and suggests an unusual JVM or non-standard Java library.
*/
@Throws(NoSuchAlgorithmException::class)
fun safeRandomBytes(numOfBytes: Int): ByteArray {
return safeRandom().generateSeed(numOfBytes)
}
/**
* Get an instance of [SecureRandom] to avoid blocking, due to waiting for additional entropy, when possible.
* In this version, the NativePRNGNonBlocking is exclusively used on Linux OS to utilize dev/urandom because in high traffic
* /dev/random may wait for a certain amount of "noise" to be generated on the host machine before returning a result.
*
* On Solaris, Linux, and OS X, if the entropy gathering device in java.security is set to file:/dev/urandom
* or file:/dev/random, then NativePRNG is preferred to SHA1PRNG. Otherwise, SHA1PRNG is preferred.
* @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html#SecureRandomImp">SecureRandom Implementation</a>.
*
* If both dev/random and dev/urandom are available, then dev/random is only preferred over dev/urandom during VM boot
* where it may be possible that OS didn't yet collect enough entropy to fill the randomness pool for the 1st time.
* @see <a href="http://www.2uo.de/myths-about-urandom/">Myths about urandom</a> for a more descriptive explanation on /dev/random Vs /dev/urandom.
* TODO: check default settings per OS and random/urandom availability.
* @return a [SecureRandom] object.
* @throws NoSuchAlgorithmException thrown if "NativePRNGNonBlocking" is not supported on the JVM
* or if no strong SecureRandom implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
* which should never happen and suggests an unusual JVM or non-standard Java library.
*/
@Throws(NoSuchAlgorithmException::class)
fun safeRandom(): SecureRandom {
if (System.getProperty("os.name") == "Linux") {
return SecureRandom.getInstance("NativePRNGNonBlocking")
} else {
return SecureRandom.getInstanceStrong()
}
}

View File

@ -0,0 +1,12 @@
package net.corda.core.crypto
import java.security.KeyFactory
/**
* Custom [KeyFactory] for EdDSA with null security [Provider].
* This is required as a [SignatureScheme] requires a [java.security.KeyFactory] property, but i2p has
* its own KeyFactory for EdDSA, thus this actually a Proxy Pattern over i2p's KeyFactory.
*/
class EdDSAKeyFactory: KeyFactory {
constructor() : super(net.i2p.crypto.eddsa.KeyFactory(), null, "EDDSA_ED25519_SHA512")
}

View File

@ -0,0 +1,71 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.opaque
import net.corda.core.serialization.serialize
import java.security.PublicKey
import java.time.Instant
import java.util.*
/**
* A [MetaData] object adds extra information to a transaction. MetaData is used to support a universal
* digital signature model enabling full, partial, fully or partially blind and metaData attached signatures,
* (such as an attached timestamp). A MetaData object contains both the merkle root of the transaction and the signer's public key.
* When signatureType is set to FULL, then visibleInputs and signedInputs can be ignored.
* Note: We could omit signatureType as it can always be defined by combining visibleInputs and signedInputs,
* but it helps to speed up the process when FULL is used, and thus we can bypass the extra check on boolean arrays.
*
* @param schemeCodeName a signature scheme's code name (e.g. ECDSA_SECP256K1_SHA256).
* @param versionID DLT's version.
* @param signatureType type of the signature, see [SignatureType] (e.g. FULL, PARTIAL, BLIND, PARTIAL_AND_BLIND).
* @param timestamp the signature's timestamp as provided by the signer.
* @param visibleInputs for partially/fully blind signatures. We use Merkle tree boolean index flags (from left to right)
* indicating what parts of the transaction were visible when the signature was calculated.
* @param signedInputs for partial signatures. We use Merkle tree boolean index flags (from left to right)
* indicating what parts of the Merkle tree are actually signed.
* @param merkleRoot the Merkle root of the transaction.
* @param publicKey the signer's public key.
*/
@CordaSerializable
open class MetaData(
val schemeCodeName: String,
val versionID: String,
val signatureType: SignatureType = SignatureType.FULL,
val timestamp: Instant?,
val visibleInputs: BitSet?,
val signedInputs: BitSet?,
val merkleRoot: ByteArray,
val publicKey: PublicKey) {
fun bytes() = this.serialize().bytes
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as MetaData
if (schemeCodeName != other.schemeCodeName) return false
if (versionID != other.versionID) return false
if (signatureType != other.signatureType) return false
if (timestamp != other.timestamp) return false
if (visibleInputs != other.visibleInputs) return false
if (signedInputs != other.signedInputs) return false
if (merkleRoot.opaque() != other.merkleRoot.opaque()) return false
if (publicKey != other.publicKey) return false
return true
}
override fun hashCode(): Int {
var result = schemeCodeName.hashCode()
result = 31 * result + versionID.hashCode()
result = 31 * result + signatureType.hashCode()
result = 31 * result + (timestamp?.hashCode() ?: 0)
result = 31 * result + (visibleInputs?.hashCode() ?: 0)
result = 31 * result + (signedInputs?.hashCode() ?: 0)
result = 31 * result + Arrays.hashCode(merkleRoot)
result = 31 * result + publicKey.hashCode()
return result
}
}

View File

@ -0,0 +1,42 @@
package net.corda.core.crypto
import java.security.*
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 algorithmName which signature algorithm is used (e.g. RSA, ECDSA. EdDSA, SPHINCS-256).
* @param sig the [Signature] class that provides the functionality of a digital signature scheme.
* eg. Signature.getInstance("SHA256withECDSA", "BC").
* @param keyFactory the KeyFactory for this scheme (e.g. KeyFactory.getInstance("RSA", "BC")).
* @param keyPairGenerator defines the <i>Service Provider Interface</i> (<b>SPI</b>) for the {@code KeyPairGenerator} class.
* e.g. KeyPairGenerator.getInstance("ECDSA", "BC").
* @param algSpec parameter specs for the underlying algorithm. Note that RSA is defined by the key size rather than algSpec.
* eg. ECGenParameterSpec("secp256k1").
* @param keySize the private key size (currently used for RSA only).
* @param desc a human-readable description for this scheme.
*/
data class SignatureScheme(
val schemeNumberID: Int,
val schemeCodeName: String,
val algorithmName: String,
val sig: Signature,
val keyFactory: KeyFactory,
val keyPairGenerator: KeyPairGeneratorSpi,
val algSpec: AlgorithmParameterSpec?,
val keySize: Int,
val desc: String) {
/**
* KeyPair generators are always initialized once we create them, as no re-initialization is required.
* Note that RSA is the sole algorithm initialized specifically by its supported keySize.
*/
init {
if (algSpec != null)
keyPairGenerator.initialize(algSpec, safeRandom())
else
keyPairGenerator.initialize(keySize, safeRandom())
}
}

View File

@ -0,0 +1,17 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
/**
* Supported Signature types:
* <p><ul>
* <li>FULL = signature covers whole transaction, by the convention that signing the Merkle root, it is equivalent to signing all parts of the transaction.
* <li>PARTIAL = signature covers only a part of the transaction, see [MetaData].
* <li>BLIND = when an entity blindly signs without having full knowledge on the content, see [MetaData].
* <li>PARTIAL_AND_BLIND = combined PARTIAL and BLIND in the same time.
* </ul>
*/
@CordaSerializable
enum class SignatureType {
FULL, PARTIAL, BLIND, PARTIAL_AND_BLIND
}

View File

@ -0,0 +1,24 @@
package net.corda.core.crypto
import net.corda.core.serialization.opaque
import java.security.InvalidKeyException
import java.security.PublicKey
import java.security.SignatureException
/**
* A wrapper around a digital signature accompanied with metadata, see [MetaData.Full] and [DigitalSignature].
* The signature protocol works as follows: s = sign(MetaData.hashBytes).
*/
open class TransactionSignature(val signatureData: ByteArray, val metaData: MetaData) : DigitalSignature(signatureData) {
/**
* Function to auto-verify a [MetaData] object's signature.
* Note that [MetaData] contains both public key and merkle root of the transaction.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun verify(): Boolean = Crypto.doVerify(metaData.publicKey, signatureData, metaData.bytes())
}

View File

@ -8,6 +8,7 @@ import de.javakaffee.kryoserializers.ArraysAsListSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.MetaData
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
@ -74,6 +75,9 @@ object DefaultKryoCustomizer {
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
register(MetaData::class.java, MetaDataSerializer)
register(BitSet::class.java, ReferencesAwareJavaSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
}

View File

@ -20,6 +20,8 @@ import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.nio.file.Path
import java.security.PublicKey
import java.security.spec.InvalidKeySpecException
import java.time.Instant
import java.util.*
import javax.annotation.concurrent.ThreadSafe
import kotlin.reflect.*
@ -551,3 +553,32 @@ object OrderedSerializer : Serializer<HashMap<Any, Any>>() {
return hm
}
}
/** For serialising a MetaData object. */
@ThreadSafe
object MetaDataSerializer : Serializer<MetaData>() {
override fun write(kryo: Kryo, output: Output, obj: MetaData) {
output.writeString(obj.schemeCodeName)
output.writeString(obj.versionID)
kryo.writeClassAndObject(output, obj.signatureType)
kryo.writeClassAndObject(output, obj.timestamp)
kryo.writeClassAndObject(output, obj.visibleInputs)
kryo.writeClassAndObject(output, obj.signedInputs)
output.writeBytesWithLength(obj.merkleRoot)
output.writeBytesWithLength(obj.publicKey.encoded)
}
@Suppress("UNCHECKED_CAST")
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
override fun read(kryo: Kryo, input: Input, type: Class<MetaData>): MetaData {
val schemeCodeName = input.readString()
val versionID = input.readString()
val signatureType = kryo.readClassAndObject(input) as SignatureType
val timestamp = kryo.readClassAndObject(input) as Instant?
val visibleInputs = kryo.readClassAndObject(input) as BitSet?
val signedInputs = kryo.readClassAndObject(input) as BitSet?
val merkleRoot = input.readBytesWithLength()
val publicKey = Crypto.decodePublicKey(input.readBytesWithLength(), schemeCodeName)
return MetaData(schemeCodeName, versionID, signatureType, timestamp, visibleInputs, signedInputs, merkleRoot, publicKey)
}
}

View File

@ -0,0 +1,656 @@
package net.corda.core.crypto
import com.google.common.collect.Sets
import net.i2p.crypto.eddsa.EdDSAKey
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECKey
import org.bouncycastle.jce.provider.BouncyCastleProvider
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.junit.Assert.assertNotEquals
import org.junit.Test
import java.security.KeyFactory
import java.security.Security
import java.util.*
import java.security.spec.*
import kotlin.test.*
/**
* Run tests for cryptographic algorithms
*/
class CryptoUtilsTest {
init {
Security.addProvider(BouncyCastleProvider())
Security.addProvider(BouncyCastlePQCProvider())
}
val testString = "Hello World"
val testBytes = testString.toByteArray()
// key generation test
@Test
fun `Generate key pairs`() {
// testing supported algorithms
val rsaKeyPair = Crypto.generateKeyPair("RSA_SHA256")
val ecdsaKKeyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val ecdsaRKeyPair = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val eddsaKeyPair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val sphincsKeyPair = Crypto.generateKeyPair("SPHINCS-256_SHA512")
// not null private keys
assertNotNull(rsaKeyPair.private);
assertNotNull(ecdsaKKeyPair.private);
assertNotNull(ecdsaRKeyPair.private);
assertNotNull(eddsaKeyPair.private);
assertNotNull(sphincsKeyPair.private);
// not null public keys
assertNotNull(rsaKeyPair.public);
assertNotNull(ecdsaKKeyPair.public);
assertNotNull(ecdsaRKeyPair.public);
assertNotNull(eddsaKeyPair.public);
assertNotNull(sphincsKeyPair.public);
// fail on unsupported algorithm
try {
val wrongKeyPair = Crypto.generateKeyPair("WRONG_ALG")
fail()
} catch (e: Exception) {
// expected
}
}
// full process tests
@Test
fun `RSA full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("RSA_SHA256")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
for (i in 0..signedData.size - 1) {
val b = signedData.get(i)
signedData.set(i,b.inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
signedData.set(i,b.dec())
}
}
@Test
fun `ECDSA secp256k1 full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0,signedData[0].inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
}
@Test
fun `ECDSA secp256r1 full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
}
@Test
fun `EDDSA ed25519 full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
}
@Test
fun `SPHINCS-256 full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("SPHINCS-256_SHA512")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
}
// test list of supported algorithms
@Test
fun `Check supported algorithms`() {
val algList : List<String> = Crypto.listSupportedSignatureSchemes()
val expectedAlgSet = setOf<String>("RSA_SHA256","ECDSA_SECP256K1_SHA256", "ECDSA_SECP256R1_SHA256", "EDDSA_ED25519_SHA512","SPHINCS-256_SHA512")
assertTrue { Sets.symmetricDifference(expectedAlgSet,algList.toSet()).isEmpty(); }
}
// Unfortunately, there isn't a standard way to encode/decode keys, so we need to test per case
@Test
fun `RSA encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("RSA_SHA256")
val (privKey, pubKey) = keyPair
val keyFactory = KeyFactory.getInstance("RSA", "BC")
// Encode and decode private key.
val privKey2 = keyFactory.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = keyFactory.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
}
@Test
fun `ECDSA secp256k1 encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privKey, pubKey) = keyPair
val kf = KeyFactory.getInstance("ECDSA", "BC")
// Encode and decode private key.
val privKey2 = kf.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = kf.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
}
@Test
fun `ECDSA secp256r1 encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privKey, pubKey) = keyPair
val kf = KeyFactory.getInstance("ECDSA", "BC")
// Encode and decode private key.
val privKey2 = kf.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = kf.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
}
@Test
fun `EdDSA encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val privKey: EdDSAPrivateKey = keyPair.private as EdDSAPrivateKey
val pubKey: EdDSAPublicKey = keyPair.public as EdDSAPublicKey
val kf = EdDSAKeyFactory()
// Encode and decode private key.
val privKey2 = kf.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = kf.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
}
@Test
fun `SPHINCS-256 encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("SPHINCS-256_SHA512")
val privKey: BCSphincs256PrivateKey = keyPair.private as BCSphincs256PrivateKey
val pubKey: BCSphincs256PublicKey = keyPair.public as BCSphincs256PublicKey
//1st method for encoding/decoding
val keyFactory = KeyFactory.getInstance("SPHINCS256", "BCPQC")
// Encode and decode private key.
val privKey2 = keyFactory.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = keyFactory.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
//2nd method for encoding/decoding
// Encode and decode private key.
val privKeyInfo : PrivateKeyInfo = PrivateKeyInfo.getInstance(privKey.encoded)
val decodedPrivKey = BCSphincs256PrivateKey(privKeyInfo)
// Check that decoded private key is equal to the initial one.
assertEquals(decodedPrivKey, privKey)
// Encode and decode public key.
val pubKeyInfo : SubjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(pubKey.encoded)
val extractedPubKey = BCSphincs256PublicKey(pubKeyInfo)
// Check that decoded private key is equal to the initial one.
assertEquals(extractedPubKey, pubKey)
}
@Test
fun `RSA scheme finder by key type`() {
val keyPairRSA = Crypto.generateKeyPair("RSA_SHA256")
val (privRSA, pubRSA) = keyPairRSA
assertEquals(privRSA.algorithm, "RSA")
assertEquals(pubRSA.algorithm, "RSA")
}
@Test
fun `ECDSA secp256k1 scheme finder by key type`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
// Encode and decode keys as they would be transferred.
val kf = KeyFactory.getInstance("ECDSA", "BC")
val privK1Decoded = kf.generatePrivate(PKCS8EncodedKeySpec(privK1.encoded))
val pubK1Decoded = kf.generatePublic(X509EncodedKeySpec(pubK1.encoded))
assertEquals(privK1Decoded.algorithm, "ECDSA")
assertEquals((privK1Decoded as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
assertEquals(pubK1Decoded.algorithm, "ECDSA")
assertEquals((pubK1Decoded as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
}
@Test
fun `ECDSA secp256r1 scheme finder by key type`() {
val keyPairR1 = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privR1, pubR1) = keyPairR1
assertEquals(privR1.algorithm, "ECDSA")
assertEquals((privR1 as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
assertEquals(pubR1.algorithm, "ECDSA")
assertEquals((pubR1 as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
}
@Test
fun `EdDSA scheme finder by key type`() {
val keyPairEd = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val (privEd, pubEd) = keyPairEd
assertEquals(privEd.algorithm, "EdDSA")
assertEquals((privEd as EdDSAKey).params, EdDSANamedCurveTable.getByName("ed25519-sha-512"))
assertEquals(pubEd.algorithm, "EdDSA")
assertEquals((pubEd as EdDSAKey).params, EdDSANamedCurveTable.getByName("ed25519-sha-512"))
}
@Test
fun `SPHINCS-256 scheme finder by key type`() {
val keyPairSP = Crypto.generateKeyPair("SPHINCS-256_SHA512")
val (privSP, pubSP) = keyPairSP
assertEquals(privSP.algorithm, "SPHINCS-256")
assertEquals(pubSP.algorithm, "SPHINCS-256")
}
@Test
fun `Automatic EdDSA key-type detection and decoding`() {
val keyPairEd = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val (privEd, pubEd) = keyPairEd
val encodedPrivEd = privEd.encoded
val encodedPubEd = pubEd.encoded
val decodedPrivEd = Crypto.decodePrivateKey(encodedPrivEd)
assertEquals(decodedPrivEd.algorithm, "EdDSA")
assertEquals(decodedPrivEd, privEd)
val decodedPubEd = Crypto.decodePublicKey(encodedPubEd)
assertEquals(decodedPubEd.algorithm, "EdDSA")
assertEquals(decodedPubEd, pubEd)
}
@Test
fun `Automatic ECDSA secp256k1 key-type detection and decoding`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val encodedPrivK1 = privK1.encoded
val encodedPubK1 = pubK1.encoded
val decodedPrivK1 = Crypto.decodePrivateKey(encodedPrivK1)
assertEquals(decodedPrivK1.algorithm, "ECDSA")
assertEquals(decodedPrivK1, privK1)
val decodedPubK1 = Crypto.decodePublicKey(encodedPubK1)
assertEquals(decodedPubK1.algorithm, "ECDSA")
assertEquals(decodedPubK1, pubK1)
}
@Test
fun `Automatic ECDSA secp256r1 key-type detection and decoding`() {
val keyPairR1 = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privR1, pubR1) = keyPairR1
val encodedPrivR1 = privR1.encoded
val encodedPubR1 = pubR1.encoded
val decodedPrivR1 = Crypto.decodePrivateKey(encodedPrivR1)
assertEquals(decodedPrivR1.algorithm, "ECDSA")
assertEquals(decodedPrivR1, privR1)
val decodedPubR1 = Crypto.decodePublicKey(encodedPubR1)
assertEquals(decodedPubR1.algorithm, "ECDSA")
assertEquals(decodedPubR1, pubR1)
}
@Test
fun `Automatic RSA key-type detection and decoding`() {
val keyPairRSA = Crypto.generateKeyPair("RSA_SHA256")
val (privRSA, pubRSA) = keyPairRSA
val encodedPrivRSA = privRSA.encoded
val encodedPubRSA = pubRSA.encoded
val decodedPrivRSA = Crypto.decodePrivateKey(encodedPrivRSA)
assertEquals(decodedPrivRSA.algorithm, "RSA")
assertEquals(decodedPrivRSA, privRSA)
val decodedPubRSA = Crypto.decodePublicKey(encodedPubRSA)
assertEquals(decodedPubRSA.algorithm, "RSA")
assertEquals(decodedPubRSA, pubRSA)
}
@Test
fun `Automatic SPHINCS-256 key-type detection and decoding`() {
val keyPairSP = Crypto.generateKeyPair("SPHINCS-256_SHA512")
val (privSP, pubSP) = keyPairSP
val encodedPrivSP = privSP.encoded
val encodedPubSP = pubSP.encoded
val decodedPrivSP = Crypto.decodePrivateKey(encodedPrivSP)
assertEquals(decodedPrivSP.algorithm, "SPHINCS-256")
assertEquals(decodedPrivSP, privSP)
val decodedPubSP = Crypto.decodePublicKey(encodedPubSP)
assertEquals(decodedPubSP.algorithm, "SPHINCS-256")
assertEquals(decodedPubSP, pubSP)
}
@Test
fun `Failure test between K1 and R1 keys`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val encodedPrivK1 = privK1.encoded
val decodedPrivK1 = Crypto.decodePrivateKey(encodedPrivK1)
val keyPairR1 = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privR1, pubR1) = keyPairR1
val encodedPrivR1 = privR1.encoded
val decodedPrivR1 = Crypto.decodePrivateKey(encodedPrivR1)
assertNotEquals(decodedPrivK1, decodedPrivR1)
}
@Test
fun `Decoding Failure on randomdata as key`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val encodedPrivK1 = privK1.encoded
// Test on random encoded bytes.
val fakeEncodedKey = ByteArray(encodedPrivK1.size)
val r = Random()
r.nextBytes(fakeEncodedKey)
// fail on fake key.
try {
val decodedFake = Crypto.decodePrivateKey(fakeEncodedKey)
fail()
} catch (e: Exception) {
// expected
}
}
@Test
fun `Decoding Failure on malformed keys`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val encodedPrivK1 = privK1.encoded
// fail on malformed key.
for (i in 0..encodedPrivK1.size - 1) {
val b = encodedPrivK1.get(i)
encodedPrivK1.set(i,b.inc())
try {
val decodedFake = Crypto.decodePrivateKey(encodedPrivK1)
println("OK")
fail()
} catch (e: Exception) {
// expected
}
encodedPrivK1.set(i,b.dec())
}
}
}

View File

@ -1,10 +1,7 @@
package net.corda.core.crypto
import org.junit.Test
import java.math.BigInteger
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
class EncodingUtilsTest {

View File

@ -0,0 +1,83 @@
package net.corda.core.crypto
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import org.junit.Test
import java.security.Security
import java.security.SignatureException
import java.time.Instant
import kotlin.test.assertTrue
import kotlin.test.fail
/**
* Digital signature MetaData tests
*/
class TransactionSignatureTest {
init {
Security.addProvider(BouncyCastleProvider())
Security.addProvider(BouncyCastlePQCProvider())
}
val testBytes = "12345678901234567890123456789012".toByteArray()
/** valid sign and verify. */
@Test
fun `MetaData Full sign and verify`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
// create a MetaData.Full object
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair.public)
// sign the message
val transactionSignature: TransactionSignature = keyPair.private.sign(meta)
// check auto-verification
assertTrue(transactionSignature.verify())
// check manual verification
assertTrue(keyPair.public.verify(transactionSignature))
}
/** Signing should fail, as I sign with a secpK1 key, but set schemeCodeName is set to secpR1. */
@Test(expected = IllegalArgumentException::class)
fun `MetaData Full failure wrong scheme`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256R1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair.public)
keyPair.private.sign(meta)
}
/** Verification should fail; corrupted metadata - public key has changed. */
@Test(expected = SignatureException::class)
fun `MetaData Full failure public key has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val keyPair2 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair2.public)
val transactionSignature = keyPair1.private.sign(meta)
transactionSignature.verify()
}
/** Verification should fail; corrupted metadata - clearData has changed. */
@Test(expected = SignatureException::class)
fun `MetaData Full failure clearData has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair1.public)
val transactionSignature = keyPair1.private.sign(meta)
val meta2 = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes.plus(testBytes), keyPair1.public)
val transactionSignature2 = TransactionSignature(transactionSignature.signatureData, meta2)
keyPair1.public.verify(transactionSignature2)
}
/** Verification should fail; corrupted metadata - schemeCodeName has changed from K1 to R1. */
@Test(expected = SignatureException::class)
fun `MetaData Wrong schemeCodeName has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair1.public)
val transactionSignature = keyPair1.private.sign(meta)
val meta2 = MetaData("ECDSA_SECP256R1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes.plus(testBytes), keyPair1.public)
val transactionSignature2 = TransactionSignature(transactionSignature.signatureData, meta2)
keyPair1.public.verify(transactionSignature2)
}
}

View File

@ -1,13 +1,15 @@
package net.corda.core.serialization
import com.google.common.primitives.Ints
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.signWithECDSA
import net.corda.core.crypto.*
import net.corda.core.messaging.Ack
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import org.junit.Test
import java.io.InputStream
import java.security.Security
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals
@ -94,6 +96,22 @@ class KryoTests {
assertEquals(-1, readRubbishStream.read())
}
@Test
fun `serialize - deserialize MetaData`() {
Security.addProvider(BouncyCastleProvider())
Security.addProvider(BouncyCastlePQCProvider())
val testString = "Hello World"
val testBytes = testString.toByteArray()
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val bitSet = java.util.BitSet(10)
bitSet.set(3)
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), bitSet, bitSet, testBytes, keyPair1.public)
val serializedMetaData = meta.bytes()
val meta2 = serializedMetaData.deserialize<MetaData>()
assertEquals(meta2, meta)
}
@CordaSerializable
private data class Person(val name: String, val birthday: Instant?)

View File

@ -46,6 +46,7 @@ class DefaultWhitelist : CordaPluginRegistry() {
addToWhitelist(BigDecimal::class.java)
addToWhitelist(LocalDate::class.java)
addToWhitelist(Period::class.java)
addToWhitelist(BitSet::class.java)
}
return true
}