-
Notifications
You must be signed in to change notification settings - Fork 27
Crypto library
Corda 5 is a multi-tenanted system where the tenant is defined in terms of a member or vnode. However, the Crypto Library manages keys on behalf of members as well as the cluster itself. In the case of the members (notaries, network managers, etc.) the tenant is their vnode id but for the cluster it is the 'cluster'.
- An HSM has limited capacity. The number of keys that it can store is measured in few hundreds or in low thousands.
- Most HSM interface libraries/drivers assume that there is either only one HSM instance per machine, like Utimaco, or per running process (like passing the location of the config files and drivers as environment variables, like AWS Cloud HSM), that require a dedicated crypto workers for such instances and involve some kind of message routing.
- Not all HSMs have the notion of partition. For those that have it, we treat partitions as separate instances of that HSM.
- Default Crypto Service (called Soft HSM) which is always present, has weaker security and uses a database to store the keys which are encrypted at rest.
- There could be several HSMs provided by a cluster host.
- As an instance of HSM can be shared between different tenants, we cannot accept key aliases directly from members due to high probability of a name clash.
- Support for the ability to store keys of different roles in different HSMs. For example, a Ledger key may be stored in an Utimaco HSM and the Session Initiating Key in Azure Key Vault.
- A tenant registers itself with each HSM category explicitly during the onboarding process.
The library has two groups of operations:
- Sensitive — implemented in a separate crypto worker, which does not run end user customizable code and is accessible only through a message bus. Note that the Crypto Library enables clients to invoke those operations so there is no need to work with the message bus directly:
- Key generation
- Signing
- HSM assignments
- Key lookups
- Generic crypto operations — reside directly in the consuming processes:
- Calculating digests
- Signature verification
- Providing metadata about public keys and digests, like algorithm
To enable different HSMs depending on key roles for the same tenant, there is a notion of the HSM category (or just a category) which roughly corresponds to key roles. Some of the categories are:
- ACCOUNTS
- CI
- LEDGER
- TLS
- JWT_KEY
- NOTARY
The low-level crypto service implementations (the Secure Modules layer) is tenant agnostic (like most of HSM) so access to the keys is partitioned using the tenant id by the higher API level.
The main idea behind layering is that the Crypto Library provides a basis for building specialized services on top of it, which in some cases are responsible only for the correct formatting of the requests to the Crypto Library (but not limited to that as in some case those services would need to have their own persistence, for example) - Accounts, Ledger Transactions, etc.
The CorDapp and RPC API don't have direct access to the lower implementations as they are accessible only in the Crypto Worker.
Public API:
Module | Description |
---|---|
corda-crypto | contains basic crypto primitives available for CorDapps and services targeting CorDapps directly as well as definitions for the DigestService and SignatureVerificationService . |
corda-cipher-suite | contains low level definitions that are required in order to implement integrations with various HSMs or extend capabilities of the Crypto Library. These definitions must not be made available for CorDapp level APIs |
Internal API:
Module | Description |
---|---|
:libs:crypto:crypto-core | The module contains the most basic common crypto artifacts, like constants defining standardized categories, cluster level tenants, and AES support for secrets. |
:libs:crypto:crypto-flow | The module contains artifacts which are required to build crypto operation messages and process the results for the flows - CryptoFlowOpsTransformer
|
:components:crypto:crypto-client | The module contains clients necessarily to execute configure HSMs, assign tenants, generate keys and sign - CryptoOpsClient , HSMConfigurationClient , HSMRegistrationClient (note that CryptoOpsProxyClient should not be used as that a rely client) |
The API (corda-cipher-suite) consists of low level artifacts that provide the asymmetric cryptography functionality for Corda. The main interfaces here are CipherSchemeMetadata
which provides all required metadata about the suite and CryptoService
which is the interface that has to be implemented for each supported HSM and provides the means to securely generate key pair and sign.
data class KeyScheme(
val codeName: String,
val algorithmOIDs: List<AlgorithmIdentifier>,
val providerName: String,
val algorithmName: String,
val algSpec: AlgorithmParameterSpec?,
val keySize: Int?
)
// defined in the corda-crypto module as that is publically visible to applications/CorDapp(s)
data class SignatureSpec(
val signatureName: String,
val params: AlgorithmParameterSpec? = null,
val customDigestName: DigestAlgorithmName? = null
)
In Corda5 the key scheme is separated from the signature spec (in Corda4 those parameters are tied together).
It's possible to specify that the digest should be precalculated befor signing by specifying the customDigestName
(not being part of the signing process itself) but that should be done carefully as that requires careful setting of the parameters.
Key Scheme | SOFT HSM's Supported Signature Algorithms |
---|---|
CORDA.RSA | SHA256withRSA, SHA384withRSA, SHA512withRSA, RSASSA-PSS (MGF1,SHA-256), RSASSA-PSS (MGF1,SHA-384), RSASSA-PSS (MGF1,SHA-512) |
CORDA.ECDSA.SECP256K1 | SHA256withECDSA, SHA384withECDSA, SHA512withECDSA |
CORDA.ECDSA.SECP256R1 | SHA256withECDSA, SHA384withECDSA, SHA512withECDSA |
CORDA.EDDSA.ED25519 | EdDSA (note that the digest calculation is part of the signing algorithm itself) |
CORDA.SPHINCS-256 | SHA512withSPHINCS256 |
CORDA.SM2 | SM3withSM2, SHA256withSM2 |
CORDA.GOST3410.GOST3411 | GOST3411withGOST3410 |
interface CryptoService {
fun requiresWrappingKey(): Boolean
fun supportedSchemes(): Array<SignatureScheme>
fun createWrappingKey(
masterKeyAlias: String,
failIfExists: Boolean,
context: Map<String, String>
)
fun generateKeyPair(
spec: KeyGenerationSpec,
context: Map<String, String>
): GeneratedKey
fun sign(
spec: SigningSpec,
data: ByteArray,
context: Map<String, String>
): ByteArray
}
The contexts for the key generation or signing contain at least one item (with the key 'tenantId') specifying on behalf of which tenant the operation is performed. The key generation context also has the 'category' item.
The key generation parameters are more like a hit and it is up to the HSM to decide how exactly the key will be generated - as internally to the HSM or exported as a wrapped key. Note: If the key has to be wrapped and the HSM needs to know the wrapping key, the corresponding HSM configuration should provide sufficient information about which master key alias should be used.
If the HSM will have to store the private key inside the HSM, it cannot use the provided parameter 'alias' directly as different tenants may choose the same value for that parameter. Instead the HSM must either randomly generate it or, ideally, use the tenant id, alias, and secret in order to calculate the actual alias in the deterministic way (using something like HMAC). The calculated alias must be returned as hsmAlias
so it can be used in the signing operations later.
class KeyGenerationSpec(
val signatureScheme: SignatureScheme,
val alias: String?,
val masterKeyAlias: String?,
val secret: ByteArray?
)
interface GeneratedKey {
val publicKey: PublicKey
}
class GeneratedPublicKey(
override val publicKey: PublicKey,
val hsmAlias: String
) : GeneratedKey
class GeneratedWrappedKey(
override val publicKey: PublicKey,
val keyMaterial: ByteArray,
val encodingVersion: Int
) : GeneratedKey
Each HSM device (and for HSMs which have a notion of independent partitions - each partition) must have a separate configuration entry in HSMConfigEntiity
.Then each tenant will have a hierarchical association for each category the tenant needs the keys. The hierarchy is required in order to keep the number of keys to the minimum.
The main entity is the SigningKeyEntity
, which stores information about a know key pair. All known key pairs must be recorded in the corresponding table. The record (entity) stores the public key of the pair, metadata about the pair, and the encrypted private key if the key was wrapped and exported from HSM (note that the private key is still not available outside of such HSM as it's encrypted before exporting using the wrapping key). If the key is not in that table, it means the key is not known as there is no alternative way of configuring or discovering key pairs.
HSMs are used for key generation and signing operations only:
-
Key pair generation - the lookup for an associated HSM is done by the tenant id and the category (note that the deprecatedAt must be 0 for the association which links tenant/category and the HSM)
-
Signing - as the generated key pairs are locked to the HSM which was used to generate it so the operation uses the HSM's configuration id (transitively through the two association entities) recorded in the known public key metadata (note that the deprecatedAt value does not play any role)
Before any tenant, either cluster level or vnode, can use any secure crypto operation requiring an HSM (key generation and signing), it must be associated with an HSM for each category. The association is done in a semi-automatic way; the tenant can pick whenever they want to use the SOFT HSM or any real HSM provided by the cluster operator. If the tenant chooses to use real HSM, it does not control which HSM will be assigned to it. The tenant can only provide some criteria such as the category, that the HSM must store only aliased keys (hosted by HSM without the ability to export them), or that it supports specific key schemes.