mirror of
https://github.com/corda/corda.git
synced 2025-02-20 17:33:15 +00:00
Added composite key provider for storing composite keys in keystore (#1006)
* Add unit tests around decoding composite keys (cherry picked from commit 9ccdd8e) * Start writing a Composite signature scheme (cherry picked from commit 72ac3a5) * Composite key serialisation * refactoring * * Address PR issues * * Address PR issues * * Address PR issues * * Address PR issues * fix up after rebase
This commit is contained in:
parent
5f7b8f6ec3
commit
78ecff7933
@ -10,6 +10,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import net.corda.contracts.BusinessCalendar
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
|
@ -1,6 +1,9 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeSignature
|
||||
import net.corda.core.crypto.provider.CordaObjectIdentifier
|
||||
import net.corda.core.crypto.provider.CordaSecurityProvider
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
@ -147,6 +150,22 @@ object Crypto {
|
||||
"at the cost of larger key sizes and loss of compatibility."
|
||||
)
|
||||
|
||||
/**
|
||||
* Corda composite key type
|
||||
*/
|
||||
val COMPOSITE_KEY = SignatureScheme(
|
||||
6,
|
||||
"COMPOSITE",
|
||||
AlgorithmIdentifier(CordaObjectIdentifier.compositeKey),
|
||||
emptyList(),
|
||||
CordaSecurityProvider.PROVIDER_NAME,
|
||||
CompositeKey.KEY_ALGORITHM,
|
||||
CompositeSignature.SIGNATURE_ALGORITHM,
|
||||
null,
|
||||
null,
|
||||
"Composite keys composed from individual public keys"
|
||||
)
|
||||
|
||||
/** Our default signature scheme if no algorithm is specified (e.g. for key generation). */
|
||||
val DEFAULT_SIGNATURE_SCHEME = EDDSA_ED25519_SHA512
|
||||
|
||||
@ -159,7 +178,8 @@ object Crypto {
|
||||
ECDSA_SECP256K1_SHA256,
|
||||
ECDSA_SECP256R1_SHA256,
|
||||
EDDSA_ED25519_SHA512,
|
||||
SPHINCS256_SHA256
|
||||
SPHINCS256_SHA256,
|
||||
COMPOSITE_KEY
|
||||
).associateBy { it.schemeCodeName }
|
||||
|
||||
/**
|
||||
@ -177,6 +197,7 @@ object Crypto {
|
||||
// The val is private to avoid any harmful state changes.
|
||||
private val providerMap: Map<String, Provider> = mapOf(
|
||||
BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(),
|
||||
CordaSecurityProvider.PROVIDER_NAME to CordaSecurityProvider(),
|
||||
"BCPQC" to BouncyCastlePQCProvider()) // unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it.
|
||||
|
||||
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
|
||||
@ -188,6 +209,7 @@ object Crypto {
|
||||
// This registration is needed for reading back EdDSA key from java keystore.
|
||||
// TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider.
|
||||
Security.addProvider(getBouncyCastleProvider())
|
||||
Security.addProvider(CordaSecurityProvider())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -202,7 +224,7 @@ object Crypto {
|
||||
}
|
||||
|
||||
fun findSignatureScheme(algorithm: AlgorithmIdentifier): SignatureScheme {
|
||||
return algorithmMap[normaliseAlgorithmIdentifier(algorithm)] ?: throw IllegalArgumentException("Unrecognised algorithm: ${algorithm}")
|
||||
return algorithmMap[normaliseAlgorithmIdentifier(algorithm)] ?: throw IllegalArgumentException("Unrecognised algorithm: ${algorithm.algorithm.id}")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -543,7 +565,7 @@ object Crypto {
|
||||
if (signatureScheme.algSpec != null)
|
||||
keyPairGenerator.initialize(signatureScheme.algSpec, newSecureRandom())
|
||||
else
|
||||
keyPairGenerator.initialize(signatureScheme.keySize, newSecureRandom())
|
||||
keyPairGenerator.initialize(signatureScheme.keySize!!, newSecureRandom())
|
||||
return keyPairGenerator.generateKeyPair()
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import java.math.BigInteger
|
||||
|
@ -28,5 +28,5 @@ data class SignatureScheme(
|
||||
val algorithmName: String,
|
||||
val signatureName: String,
|
||||
val algSpec: AlgorithmParameterSpec?,
|
||||
val keySize: Int,
|
||||
val keySize: Int?,
|
||||
val desc: String)
|
||||
|
@ -1,8 +1,14 @@
|
||||
package net.corda.core.crypto
|
||||
package net.corda.core.crypto.composite
|
||||
|
||||
import net.corda.core.crypto.CompositeKey.NodeAndWeight
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.crypto.provider.CordaObjectIdentifier
|
||||
import net.corda.core.crypto.toSHA256Bytes
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import org.bouncycastle.asn1.*
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.PublicKey
|
||||
@ -26,9 +32,35 @@ import java.util.*
|
||||
* signatures required) to satisfy the sub-tree rooted at this node.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class CompositeKey private constructor (val threshold: Int,
|
||||
children: List<NodeAndWeight>) : PublicKey {
|
||||
class CompositeKey private constructor(val threshold: Int, children: List<NodeAndWeight>) : PublicKey {
|
||||
companion object {
|
||||
val KEY_ALGORITHM = "COMPOSITE"
|
||||
/**
|
||||
* Build a composite key from a DER encoded form.
|
||||
*/
|
||||
fun getInstance(encoded: ByteArray) = getInstance(ASN1Primitive.fromByteArray(encoded))
|
||||
|
||||
fun getInstance(asn1: ASN1Primitive): PublicKey {
|
||||
val keyInfo = SubjectPublicKeyInfo.getInstance(asn1)
|
||||
require(keyInfo.algorithm.algorithm == CordaObjectIdentifier.compositeKey)
|
||||
val sequence = ASN1Sequence.getInstance(keyInfo.parsePublicKey())
|
||||
val threshold = ASN1Integer.getInstance(sequence.getObjectAt(0)).positiveValue.toInt()
|
||||
val sequenceOfChildren = ASN1Sequence.getInstance(sequence.getObjectAt(1))
|
||||
val builder = Builder()
|
||||
val listOfChildren = sequenceOfChildren.objects.toList()
|
||||
listOfChildren.forEach { childAsn1 ->
|
||||
require(childAsn1 is ASN1Sequence)
|
||||
val childSeq = childAsn1 as ASN1Sequence
|
||||
val key = Crypto.decodePublicKey((childSeq.getObjectAt(0) as DERBitString).bytes)
|
||||
val weight = ASN1Integer.getInstance(childSeq.getObjectAt(1))
|
||||
builder.addKey(key, weight.positiveValue.toInt())
|
||||
}
|
||||
return builder.build(threshold)
|
||||
}
|
||||
}
|
||||
|
||||
val children = children.sorted()
|
||||
|
||||
init {
|
||||
// TODO: replace with the more extensive, but slower, checkValidity() test.
|
||||
checkConstraints()
|
||||
@ -47,8 +79,9 @@ class CompositeKey private constructor (val threshold: Int,
|
||||
require(threshold > 0) { "CompositeKey threshold is set to $threshold, but it should be a positive integer." }
|
||||
// If threshold is bigger than total weight, then it will never be satisfied.
|
||||
val totalWeight = totalWeight()
|
||||
require(threshold <= totalWeight) { "CompositeKey threshold: $threshold cannot be bigger than aggregated weight of " +
|
||||
"child nodes: $totalWeight"}
|
||||
require(threshold <= totalWeight) {
|
||||
"CompositeKey threshold: $threshold cannot be bigger than aggregated weight of child nodes: $totalWeight"
|
||||
}
|
||||
}
|
||||
|
||||
// Graph cycle detection in the composite key structure to avoid infinite loops on CompositeKey graph traversal and
|
||||
@ -75,7 +108,7 @@ class CompositeKey private constructor (val threshold: Int,
|
||||
* TODO: Always call this method when deserialising [CompositeKey]s.
|
||||
*/
|
||||
fun checkValidity() {
|
||||
val visitedMap = IdentityHashMap<CompositeKey,Boolean>()
|
||||
val visitedMap = IdentityHashMap<CompositeKey, Boolean>()
|
||||
visitedMap.put(this, true)
|
||||
cycleDetection(visitedMap) // Graph cycle testing on the root node.
|
||||
checkConstraints()
|
||||
@ -93,7 +126,7 @@ class CompositeKey private constructor (val threshold: Int,
|
||||
private fun totalWeight(): Int {
|
||||
var sum = 0
|
||||
for ((_, weight) in children) {
|
||||
require (weight > 0) { "Non-positive weight: $weight detected." }
|
||||
require(weight > 0) { "Non-positive weight: $weight detected." }
|
||||
sum = Math.addExact(sum, weight) // Add and check for integer overflow.
|
||||
}
|
||||
return sum
|
||||
@ -104,17 +137,17 @@ class CompositeKey private constructor (val threshold: Int,
|
||||
* Each node should be assigned with a positive weight to avoid certain types of weight underflow attacks.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class NodeAndWeight(val node: PublicKey, val weight: Int): Comparable<NodeAndWeight>, ASN1Object() {
|
||||
|
||||
data class NodeAndWeight(val node: PublicKey, val weight: Int) : Comparable<NodeAndWeight>, ASN1Object() {
|
||||
init {
|
||||
// We don't allow zero or negative weights. Minimum weight = 1.
|
||||
require (weight > 0) { "A non-positive weight was detected. Node info: $this" }
|
||||
require(weight > 0) { "A non-positive weight was detected. Node info: $this" }
|
||||
}
|
||||
|
||||
override fun compareTo(other: NodeAndWeight): Int {
|
||||
return if (weight == other.weight) {
|
||||
return if (weight == other.weight)
|
||||
ByteBuffer.wrap(node.toSHA256Bytes()).compareTo(ByteBuffer.wrap(other.node.toSHA256Bytes()))
|
||||
} else weight.compareTo(other.weight)
|
||||
else
|
||||
weight.compareTo(other.weight)
|
||||
}
|
||||
|
||||
override fun toASN1Primitive(): ASN1Primitive {
|
||||
@ -129,16 +162,13 @@ class CompositeKey private constructor (val threshold: Int,
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ALGORITHM = CompositeSignature.ALGORITHM_IDENTIFIER.algorithm.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes single PublicKey and checks if CompositeKey requirements hold for that key.
|
||||
*/
|
||||
fun isFulfilledBy(key: PublicKey) = isFulfilledBy(setOf(key))
|
||||
|
||||
override fun getAlgorithm() = ALGORITHM
|
||||
override fun getAlgorithm() = KEY_ALGORITHM
|
||||
|
||||
override fun getEncoded(): ByteArray {
|
||||
val keyVector = ASN1EncodableVector()
|
||||
val childrenVector = ASN1EncodableVector()
|
||||
@ -147,13 +177,14 @@ class CompositeKey private constructor (val threshold: Int,
|
||||
}
|
||||
keyVector.add(ASN1Integer(threshold.toLong()))
|
||||
keyVector.add(DERSequence(childrenVector))
|
||||
return SubjectPublicKeyInfo(CompositeSignature.ALGORITHM_IDENTIFIER, DERSequence(keyVector)).encoded
|
||||
return SubjectPublicKeyInfo(AlgorithmIdentifier(CordaObjectIdentifier.compositeKey), DERSequence(keyVector)).encoded
|
||||
}
|
||||
|
||||
override fun getFormat() = ASN1Encoding.DER
|
||||
|
||||
// Extracted method from isFulfilledBy.
|
||||
private fun checkFulfilledBy(keysToCheck: Iterable<PublicKey>): Boolean {
|
||||
if (keysToCheck.any { it is CompositeKey } ) return false
|
||||
if (keysToCheck.any { it is CompositeKey }) return false
|
||||
val totalWeight = children.map { (node, weight) ->
|
||||
if (node is CompositeKey) {
|
||||
if (node.checkFulfilledBy(keysToCheck)) weight else 0
|
||||
@ -221,18 +252,18 @@ class CompositeKey private constructor (val threshold: Int,
|
||||
* Builds the [CompositeKey]. If [threshold] is not specified, it will default to
|
||||
* the total (aggregated) weight of the children, effectively generating an "N of N" requirement.
|
||||
* During process removes single keys wrapped in [CompositeKey] and enforces ordering on child nodes.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun build(threshold: Int? = null): PublicKey {
|
||||
val n = children.size
|
||||
if (n > 1)
|
||||
return CompositeKey(threshold ?: children.map { (_, weight) -> weight }.sum(), children)
|
||||
return if (n > 1)
|
||||
CompositeKey(threshold ?: children.map { (_, weight) -> weight }.sum(), children)
|
||||
else if (n == 1) {
|
||||
require(threshold == null || threshold == children.first().weight)
|
||||
{ "Trying to build invalid CompositeKey, threshold value different than weight of single child node." }
|
||||
return children.first().node // We can assume that this node is a correct CompositeKey.
|
||||
}
|
||||
else throw IllegalArgumentException("Trying to build CompositeKey without child nodes.")
|
||||
{ "Trying to build invalid CompositeKey, threshold value different than weight of single child node." }
|
||||
children.first().node // We can assume that this node is a correct CompositeKey.
|
||||
} else throw IllegalArgumentException("Trying to build CompositeKey without child nodes.")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.corda.core.crypto
|
||||
package net.corda.core.crypto.composite
|
||||
|
||||
import net.corda.core.serialization.deserialize
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
@ -10,14 +10,10 @@ import java.security.spec.AlgorithmParameterSpec
|
||||
/**
|
||||
* Dedicated class for storing a set of signatures that comprise [CompositeKey].
|
||||
*/
|
||||
class CompositeSignature : Signature(ALGORITHM) {
|
||||
class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
|
||||
companion object {
|
||||
val ALGORITHM = "2.25.30086077608615255153862931087626791003"
|
||||
// UUID-based OID
|
||||
// TODO: Register for an OID space and issue our own shorter OID
|
||||
val ALGORITHM_IDENTIFIER = AlgorithmIdentifier(ASN1ObjectIdentifier(ALGORITHM))
|
||||
|
||||
fun getService(provider: Provider) = Provider.Service(provider, "Signature", ALGORITHM, CompositeSignature::class.java.name, emptyList(), emptyMap())
|
||||
val SIGNATURE_ALGORITHM = "COMPOSITESIG"
|
||||
fun getService(provider: Provider) = Provider.Service(provider, "Signature", SIGNATURE_ALGORITHM, CompositeSignature::class.java.name, emptyList(), emptyMap())
|
||||
}
|
||||
|
||||
private var signatureState: State? = null
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.crypto
|
||||
package net.corda.core.crypto.composite
|
||||
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
||||
/**
|
@ -0,0 +1,34 @@
|
||||
package net.corda.core.crypto.composite
|
||||
|
||||
import java.security.*
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import java.security.spec.KeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
class KeyFactory : KeyFactorySpi() {
|
||||
|
||||
@Throws(InvalidKeySpecException::class)
|
||||
override fun engineGeneratePrivate(keySpec: KeySpec): PrivateKey {
|
||||
// Private composite key not supported.
|
||||
throw InvalidKeySpecException("key spec not recognised: " + keySpec.javaClass)
|
||||
}
|
||||
|
||||
@Throws(InvalidKeySpecException::class)
|
||||
override fun engineGeneratePublic(keySpec: KeySpec): PublicKey? {
|
||||
return when (keySpec) {
|
||||
is X509EncodedKeySpec -> CompositeKey.getInstance(keySpec.encoded)
|
||||
else -> throw InvalidKeySpecException("key spec not recognised: " + keySpec.javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(InvalidKeySpecException::class)
|
||||
override fun <T : KeySpec> engineGetKeySpec(key: Key, keySpec: Class<T>): T {
|
||||
// Only support [X509EncodedKeySpec].
|
||||
throw InvalidKeySpecException("Not implemented yet $key $keySpec")
|
||||
}
|
||||
|
||||
@Throws(InvalidKeyException::class)
|
||||
override fun engineTranslateKey(key: Key): Key {
|
||||
throw InvalidKeyException("No other composite key providers known")
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.corda.core.crypto.provider
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeSignature
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||
import java.security.AccessController
|
||||
import java.security.PrivilegedAction
|
||||
import java.security.Provider
|
||||
|
||||
class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") {
|
||||
companion object {
|
||||
val PROVIDER_NAME = "Corda"
|
||||
}
|
||||
|
||||
init {
|
||||
AccessController.doPrivileged(PrivilegedAction<Unit> { setup() })
|
||||
}
|
||||
|
||||
private fun setup() {
|
||||
put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.composite.KeyFactory")
|
||||
put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.composite.CompositeSignature")
|
||||
|
||||
val compositeKeyOID = CordaObjectIdentifier.compositeKey.id
|
||||
put("Alg.Alias.KeyFactory.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
|
||||
put("Alg.Alias.KeyFactory.OID.$compositeKeyOID", CompositeKey.KEY_ALGORITHM)
|
||||
put("Alg.Alias.Signature.$compositeKeyOID", CompositeSignature.SIGNATURE_ALGORITHM)
|
||||
put("Alg.Alias.Signature.OID.$compositeKeyOID", CompositeSignature.SIGNATURE_ALGORITHM)
|
||||
}
|
||||
}
|
||||
|
||||
object CordaObjectIdentifier {
|
||||
// UUID-based OID
|
||||
// TODO: Register for an OID space and issue our own shorter OID
|
||||
val compositeKey = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791002")
|
||||
val compositeSignature = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
|
||||
}
|
@ -3,7 +3,7 @@ package net.corda.core.node.services
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.keys
|
||||
|
@ -8,7 +8,7 @@ import de.javakaffee.kryoserializers.ArraysAsListSerializer
|
||||
import de.javakaffee.kryoserializers.BitSetSerializer
|
||||
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
|
||||
import de.javakaffee.kryoserializers.guava.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.MetaData
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
@ -9,6 +9,7 @@ import com.esotericsoftware.kryo.util.MapReferenceResolver
|
||||
import com.google.common.annotations.VisibleForTesting
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.AttachmentsClassLoader
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
|
@ -2,7 +2,7 @@ package net.corda.core.contracts
|
||||
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.sign
|
||||
|
@ -1,14 +1,25 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeSignature
|
||||
import net.corda.core.crypto.composite.CompositeSignaturesWithKeys
|
||||
import net.corda.core.div
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CompositeKeyTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder: TemporaryFolder = TemporaryFolder()
|
||||
|
||||
val aliceKey = generateKeyPair()
|
||||
val bobKey = generateKeyPair()
|
||||
val charlieKey = generateKeyPair()
|
||||
@ -65,7 +76,7 @@ class CompositeKeyTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `encoded tree decodes correctly`() {
|
||||
fun `kryo encoded tree decodes correctly`() {
|
||||
val aliceAndBob = CompositeKey.Builder().addKeys(alicePublicKey, bobPublicKey).build()
|
||||
val aliceAndBobOrCharlie = CompositeKey.Builder().addKeys(aliceAndBob, charliePublicKey).build(threshold = 1)
|
||||
|
||||
@ -75,6 +86,35 @@ class CompositeKeyTests {
|
||||
assertEquals(decoded, aliceAndBobOrCharlie)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `der encoded tree decodes correctly`() {
|
||||
val aliceAndBob = CompositeKey.Builder().addKeys(alicePublicKey, bobPublicKey).build()
|
||||
val aliceAndBobOrCharlie = CompositeKey.Builder().addKeys(aliceAndBob, charliePublicKey).build(threshold = 1)
|
||||
|
||||
val encoded = aliceAndBobOrCharlie.encoded
|
||||
val decoded = CompositeKey.getInstance(encoded)
|
||||
|
||||
assertEquals(decoded, aliceAndBobOrCharlie)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `der encoded tree decodes correctly with weighting`() {
|
||||
val aliceAndBob = CompositeKey.Builder()
|
||||
.addKey(alicePublicKey, 2)
|
||||
.addKey(bobPublicKey, 1)
|
||||
.build(threshold = 2)
|
||||
|
||||
val aliceAndBobOrCharlie = CompositeKey.Builder()
|
||||
.addKey(aliceAndBob, 3)
|
||||
.addKey(charliePublicKey, 2)
|
||||
.build(threshold = 3)
|
||||
|
||||
val encoded = aliceAndBobOrCharlie.encoded
|
||||
val decoded = CompositeKey.getInstance(encoded)
|
||||
|
||||
assertEquals(decoded, aliceAndBobOrCharlie)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tree canonical form`() {
|
||||
assertEquals(CompositeKey.Builder().addKeys(alicePublicKey).build(), alicePublicKey)
|
||||
@ -260,6 +300,59 @@ class CompositeKeyTests {
|
||||
assertFalse { compositeKey.isFulfilledBy(signaturesWithoutRSA.byKeys()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Test save to keystore`() {
|
||||
// From test case [CompositeKey from multiple signature schemes and signature verification]
|
||||
val (privRSA, pubRSA) = Crypto.generateKeyPair(Crypto.RSA_SHA256)
|
||||
val (privK1, pubK1) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256)
|
||||
val (privR1, pubR1) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val (privEd, pubEd) = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
|
||||
val (privSP, pubSP) = Crypto.generateKeyPair(Crypto.SPHINCS256_SHA256)
|
||||
|
||||
val RSASignature = privRSA.sign(message.bytes, pubRSA)
|
||||
val K1Signature = privK1.sign(message.bytes, pubK1)
|
||||
val R1Signature = privR1.sign(message.bytes, pubR1)
|
||||
val EdSignature = privEd.sign(message.bytes, pubEd)
|
||||
val SPSignature = privSP.sign(message.bytes, pubSP)
|
||||
|
||||
val compositeKey = CompositeKey.Builder().addKeys(pubRSA, pubK1, pubR1, pubEd, pubSP).build() as CompositeKey
|
||||
|
||||
val signatures = listOf(RSASignature, K1Signature, R1Signature, EdSignature, SPSignature)
|
||||
assertTrue { compositeKey.isFulfilledBy(signatures.byKeys()) }
|
||||
// One signature is missing.
|
||||
val signaturesWithoutRSA = listOf(K1Signature, R1Signature, EdSignature, SPSignature)
|
||||
assertFalse { compositeKey.isFulfilledBy(signaturesWithoutRSA.byKeys()) }
|
||||
|
||||
// Create self sign CA.
|
||||
val caKeyPair = Crypto.generateKeyPair()
|
||||
val ca = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test CA"), caKeyPair)
|
||||
|
||||
// Sign the composite key with the self sign CA.
|
||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca, caKeyPair, X500Name("CN=CompositeKey"), compositeKey)
|
||||
|
||||
// Store certificate to keystore.
|
||||
val keystorePath = tempFolder.root.toPath() / "keystore.jks"
|
||||
val keystore = KeyStoreUtilities.loadOrCreateKeyStore(keystorePath, "password")
|
||||
keystore.setCertificateEntry("CompositeKey", compositeKeyCert.cert)
|
||||
keystore.save(keystorePath, "password")
|
||||
|
||||
// Load keystore from disk.
|
||||
val keystore2 = KeyStoreUtilities.loadKeyStore(keystorePath, "password")
|
||||
assertTrue { keystore2.containsAlias("CompositeKey") }
|
||||
|
||||
val key = keystore2.getCertificate("CompositeKey").publicKey
|
||||
// Convert sun public key to Composite key.
|
||||
val compositeKey2 = Crypto.toSupportedPublicKey(key)
|
||||
assertTrue { compositeKey2 is CompositeKey }
|
||||
|
||||
// Run the same composite key test again.
|
||||
assertTrue { compositeKey2.isFulfilledBy(signatures.byKeys()) }
|
||||
assertFalse { compositeKey2.isFulfilledBy(signaturesWithoutRSA.byKeys()) }
|
||||
|
||||
// Ensure keys are the same before and after keystore.
|
||||
assertEquals(compositeKey, compositeKey2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CompositeKey deterministic children sorting`() {
|
||||
val (_, pub1) = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
|
||||
|
@ -344,7 +344,7 @@ class CryptoUtilsTest {
|
||||
@Test
|
||||
fun `Check supported algorithms`() {
|
||||
val algList: List<String> = Crypto.supportedSignatureSchemes.keys.toList()
|
||||
val expectedAlgSet = setOf("RSA_SHA256", "ECDSA_SECP256K1_SHA256", "ECDSA_SECP256R1_SHA256", "EDDSA_ED25519_SHA512", "SPHINCS-256_SHA512")
|
||||
val expectedAlgSet = setOf("RSA_SHA256", "ECDSA_SECP256K1_SHA256", "ECDSA_SECP256R1_SHA256", "EDDSA_ED25519_SHA512", "SPHINCS-256_SHA512", "COMPOSITE")
|
||||
assertTrue { Sets.symmetricDifference(expectedAlgSet, algList.toSet()).isEmpty(); }
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import io.requery.sql.*
|
||||
import io.requery.sql.platform.Generic
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.toBase58String
|
||||
|
@ -4,7 +4,8 @@ import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionType
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.div
|
||||
import net.corda.core.getOrThrow
|
||||
@ -22,7 +23,6 @@ import net.corda.node.services.transactions.minClusterSize
|
||||
import net.corda.node.services.transactions.minCorrectReplicas
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.node.utilities.transaction
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.After
|
||||
|
@ -10,6 +10,7 @@ import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.node.utilities
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.serialize
|
||||
|
@ -2,6 +2,7 @@ package net.corda.testing
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.composite.expandedCompositeKeys
|
||||
import net.corda.core.crypto.testing.NullSignature
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
|
Loading…
x
Reference in New Issue
Block a user