mirror of
https://github.com/corda/corda.git
synced 2024-12-21 05:53:23 +00:00
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:
parent
dc8c6747d3
commit
4a9ff84fc7
421
core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
Normal file
421
core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
Normal 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()
|
||||
}
|
117
core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
Normal file
117
core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
Normal 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()
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
71
core/src/main/kotlin/net/corda/core/crypto/MetaData.kt
Normal file
71
core/src/main/kotlin/net/corda/core/crypto/MetaData.kt
Normal 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
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
17
core/src/main/kotlin/net/corda/core/crypto/SignatureType.kt
Normal file
17
core/src/main/kotlin/net/corda/core/crypto/SignatureType.kt
Normal 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
|
||||
}
|
@ -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())
|
||||
}
|
@ -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) }
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
656
core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt
Normal file
656
core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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?)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user