Skip to content

Commit

Permalink
FABN-1666 Allow HSM identity to be stored (#377) (#392)
Browse files Browse the repository at this point in the history
Allow HSM based identities to be stored and retrieved

Signed-off-by: Bret Harrison <[email protected]>
  • Loading branch information
harrisob authored Dec 4, 2020
1 parent 9d74b64 commit cfbfd24
Show file tree
Hide file tree
Showing 21 changed files with 282 additions and 78 deletions.
39 changes: 31 additions & 8 deletions docs/tutorials/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ if (identity && identity.type === 'X.509') {

## Using a Hardware Security Module

The SDK uses the [PKCS #11](https://en.wikipedia.org/wiki/PKCS_11) interface to make use of Hardware Security Module
(HSM) devices for key management. Identities using an HSM-managed private key are similar to an X.509 identity but
with the private key omitted. However, in order to use HSM-managed identities the containing wallet must be configured
with details of the HSM that holds the private key. This is achieved by registering an `IdentityProvider` with the
wallet, for example:
The SDK uses the [PKCS #11](https://en.wikipedia.org/wiki/PKCS_11) interface to
make use of Hardware Security Module (HSM) devices for key management. Identities
using an HSM-managed private key are similar to an X.509 identity but with the
private key omitted (a handle to the key is used instead).
In order to use HSM-managed identities the containing wallet must be configured
with details of the HSM that holds the private key. This is achieved by registering
an `IdentityProvider` with the wallet, for example:
```javascript
const hsmProvider = new HsmX509Provider({
lib: '/path/to/hsm-specific/pkcs11/library',
Expand All @@ -78,12 +80,33 @@ const hsmProvider = new HsmX509Provider({
wallet.getProviderRegistry().addProvider(hsmProvider);
```

Once the wallet has been configured with details of the HSM, HSM-managed identities can be stored and retreived from
the wallet as normal, for example:
Once the wallet has been confgured with details of the HSM, the crypto suite being
used by the provider may be assigned to a new fabric certificate authority instance.
The crypto suite will have been initialized with the `hsmProvider` option values
( lib, pin, slot ) and it has opened a session with the HSM.

```
const hsmCAClient = new FabricCAClient(
'http://localhost:7054',
{trustedRoots: [], verify: false};,
'ca-org1', hsmProvider.getCryptoSuite()
);
const enrollmentResults = await hsmCAClient.enroll(options);
```

HSM-managed identities can be stored and retreived from the wallet. When storing
an indentity, use the actual key returned in the enrollment. This key will contain
all the information needed for the identity's credentials to be access on the HSM
for signing requests.

```javascript
const identity: HsmX509Identity = {
credentials: {
certificate: 'PEM format certificate string',
certificate: enrollmentResults.certificate,
// PEM format certificate string
privateKey: enrollmentResults.key.toBytes()
// PKCS11 key generated by the crypto suite
// with the HSM handles to the actual keys
},
mspId: 'org1',
type: 'HSM-X.509',
Expand Down
38 changes: 2 additions & 36 deletions fabric-ca-client/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {ICryptoSuite, User} from 'fabric-common';
import {ICryptoSuite, ICryptoKey, User} from 'fabric-common';

declare class FabricCAServices {
constructor(url: string | FabricCAServices.IFabricCAService, tlsOptions?: FabricCAServices.TLSOptions, caName?: string, cryptoSuite?: ICryptoSuite);
Expand Down Expand Up @@ -69,42 +69,8 @@ declare namespace FabricCAServices {
csr?: string;
}

export interface IKey {
getSKI(): string;

/**
* Returns true if this key is a symmetric key, false is this key is asymmetric
*
* @returns {boolean} if this key is a symmetric key
*/
isSymmetric(): boolean;

/**
* Returns true if this key is an asymmetric private key, false otherwise.
*
* @returns {boolean} if this key is an asymmetric private key
*/
isPrivate(): boolean;

/**
* Returns the corresponding public key if this key is an asymmetric private key.
* If this key is already public, returns this key itself.
*
* @returns {module:api.Key} the corresponding public key if this key is an asymmetric private key.
* If this key is already public, returns this key itself.
*/
getPublicKey(): IKey;

/**
* Converts this key to its PEM representation, if this operation is allowed.
*
* @returns {string} the PEM string representation of the key
*/
toBytes(): string;
}

export interface IEnrollResponse {
key: IKey;
key: ICryptoKey;
certificate: string;
rootCertificate: string;
}
Expand Down
2 changes: 2 additions & 0 deletions fabric-common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const CryptoSuite = require('./lib/CryptoSuite');
const HashPrimitives = require('./lib/HashPrimitives');
const Identity = require('./lib/Identity');
const Key = require('./lib/Key');
const Pkcs11EcdsaKey = require('./lib/impl/ecdsa/pkcs11_key');
const KeyValueStore = require('./lib/KeyValueStore');
const Signer = require('./lib/Signer');
const SigningIdentity = require('./lib/SigningIdentity');
Expand Down Expand Up @@ -47,6 +48,7 @@ module.exports = {
HashPrimitives,
Identity,
Key,
Pkcs11EcdsaKey,
KeyValueStore,
Signer,
SigningIdentity,
Expand Down
9 changes: 9 additions & 0 deletions fabric-common/lib/CryptoSuite.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ class CryptoSuite {
getKey(ski) {
}

/**
* Returns the key size this implementation uses when generating new keys.
*
* @returns {number} key size
*/
getKeySize() {
return this._keySize;
}

/**
* Produce a hash of the message <code>msg</code> using options <code>opts</code>
*
Expand Down
7 changes: 7 additions & 0 deletions fabric-common/lib/Key.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ class Key {
*/
getSKI() {}

/**
* Returns the key's HSM handle in string format
*
* @returns {string} The handle identifier of this key as a hexidecial encoded string
*/
getHandle() {}

/**
* Returns true if this key is a symmetric key, false is this key is asymmetric
*
Expand Down
14 changes: 13 additions & 1 deletion fabric-common/lib/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const TYPE = 'User';
const Identity = require('./Identity');
const Signer = require('./Signer');
const SigningIdentity = require('./SigningIdentity');
const Pkcs11EcdsaKey = require('./impl/ecdsa/pkcs11_key');
const CryptoSuite_PKCS11 = require('./impl/bccsp_pkcs11');
const sdkUtils = require('./Utils');
const logger = sdkUtils.getLogger(TYPE);
const check = sdkUtils.checkParameter;
Expand Down Expand Up @@ -206,7 +208,17 @@ const User = class {
this._cryptoSuite.setCryptoKeyStore(sdkUtils.newCryptoKeyStore());
}

const pubKey = await this._cryptoSuite.createKeyFromRaw(certificate);
let pubKey;
// when the crypto suite and key are PKCS11 (HSM based) then we
// need to handle the public key differently
if (this._cryptoSuite instanceof CryptoSuite_PKCS11 && privateKey instanceof Pkcs11EcdsaKey) {
pubKey = privateKey.getPublicKey();
if (!pubKey) {
pubKey = await this._cryptoSuite.createKeyFromRaw(certificate);
}
} else {
pubKey = await this._cryptoSuite.createKeyFromRaw(certificate);
}

this._identity = new Identity(certificate, pubKey, mspId, this._cryptoSuite);
this._signingIdentity = new SigningIdentity(certificate, pubKey, mspId, this._cryptoSuite, new Signer(this._cryptoSuite, privateKey));
Expand Down
7 changes: 7 additions & 0 deletions fabric-common/lib/impl/CryptoSuite_ECDSA_AES.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ class CryptoSuite_ECDSA_AES extends CryptoSuite {
}
}

/**
* This is an implementation of {@link module:api.CryptoSuite#getKeySize}
*/
getKeySize() {
return this._keySize;
}

/**
* This is an implementation of {@link module:api.CryptoSuite#hash}
* The opts argument is not supported.
Expand Down
6 changes: 5 additions & 1 deletion fabric-common/lib/impl/aes/pkcs11_key.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

'use strict';

const {Key} = require('../../../');
const Key = require('../../Key');

const PKCS11_AES_KEY = class extends Key {

Expand Down Expand Up @@ -46,6 +46,10 @@ const PKCS11_AES_KEY = class extends Key {
return this._ski;
}

getHandle() {
return this._handle.toString('hex');
}

isSymmetric() {
return true;
}
Expand Down
20 changes: 15 additions & 5 deletions fabric-common/lib/impl/bccsp_pkcs11.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

'use strict';

const {CryptoAlgorithms, CryptoSuite, HashPrimitives, Utils: utils} = require('../../');
const CryptoAlgorithms = require('../CryptoAlgorithms');
const CryptoSuite = require('../CryptoSuite');
const HashPrimitives = require('../HashPrimitives');
const utils = require('../Utils');

const aesKey = require('./aes/pkcs11_key.js');
const ecdsaKey = require('./ecdsa/pkcs11_key.js');
Expand Down Expand Up @@ -762,7 +765,7 @@ class CryptoSuite_PKCS11 extends CryptoSuite {

/**
* This is an implementation of {@link module:api.CryptoSuite#generateEphemeralKey}
* @returns {module:api.Key} Promise of an instance of {@link module:PKCS11_ECDSA_KEY}
* @returns {module:api.Key} Promise of an instance of {@link module:Pkcs11EcdsaKey}
* containing the private key and the public key.
*/
generateEphemeralKey(opts = {}) {
Expand Down Expand Up @@ -804,7 +807,7 @@ class CryptoSuite_PKCS11 extends CryptoSuite {
* across PKCS11 sessions by the HSM hardware. Use generateEphemeralKey to
* retrieve an ephmeral key.
*
* @returns {module:api.Key} Promise of an instance of {@link module:PKCS11_ECDSA_KEY}
* @returns {module:api.Key} Promise of an instance of {@link module:Pkcs11EcdsaKey}
* containing the private key and the public key.
*/
generateKey(opts) {
Expand Down Expand Up @@ -894,6 +897,13 @@ class CryptoSuite_PKCS11 extends CryptoSuite {
}));
}

/**
* This is an implementation of {@link module:api.CryptoSuite#getKeySize}
*/
getKeySize() {
return this._keySize;
}

/**
* This is an implementation of {@link module:api.CryptoSuite#sign}
* Signs digest using key k.
Expand All @@ -902,7 +912,7 @@ class CryptoSuite_PKCS11 extends CryptoSuite {
sign(key, digest) {
if (typeof key === 'undefined' || key === null ||
!(key instanceof ecdsaKey) || !key.isPrivate()) {
throw new Error(__func() + ' key must be PKCS11_ECDSA_KEY type private key');
throw new Error(__func() + ' key must be Pkcs11EcdsaKey type private key');
}
if (typeof digest === 'undefined' || digest === null ||
!(digest instanceof Buffer)) {
Expand All @@ -919,7 +929,7 @@ class CryptoSuite_PKCS11 extends CryptoSuite {
verify(key, signature, digest) {
if (typeof key === 'undefined' || key === null ||
!(key instanceof ecdsaKey || key instanceof ECDSAKey)) {
throw new Error(__func() + ' key must be PKCS11_ECDSA_KEY type or ECDSA_KEY type');
throw new Error(__func() + ' key must be Pkcs11EcdsaKey type or ECDSA_KEY type');
}
if (typeof signature === 'undefined' || signature === null ||
!(signature instanceof Buffer)) {
Expand Down
10 changes: 10 additions & 0 deletions fabric-common/lib/impl/ecdsa/key.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ class ECDSA_KEY extends Key {
return HashPrimitives.SHA2_256(buff);
}

/**
* Not supported by non PKCS11 keys.
* Only PKCS11 keys have a handle used by the HSM internally to access the key.
*
* @throws Error
*/
getHandle() {
throw Error('This key does not have a PKCS11 handle');
}

isSymmetric() {
return false;
}
Expand Down
37 changes: 24 additions & 13 deletions fabric-common/lib/impl/ecdsa/pkcs11_key.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

'use strict';

const {Key} = require('../../../');
const Key = require('../../Key');
const jsrsa = require('jsrsasign');
const asn1 = jsrsa.asn1;

Expand All @@ -19,19 +19,16 @@ const EC = elliptic.ec;
* This module implements the {@link module:api.Key} interface, for ECDSA key management
* by Hardware Security Module support via PKCS#11 interface.
*
* @class PKCS11_ECDSA_KEY
* @class Pkcs11EcdsaKey
* @extends module:api.Key
*/
const PKCS11_ECDSA_KEY = class extends Key {
const Pkcs11EcdsaKey = class extends Key {

constructor(attr, size) {
if (typeof attr === 'undefined' || attr === null) {
throw new Error('constructor: attr parameter must be specified');
}
if (typeof attr.ski === 'undefined' || attr.ski === null) {
throw new Error('constructor: invalid key SKI');
}
if (!(attr.ski instanceof Buffer)) {
if (typeof attr.ski !== 'undefined' && attr.ski !== null && !(attr.ski instanceof Buffer)) {
throw new Error('constructor: key SKI must be Buffer type');
}
if ((typeof attr.priv === 'undefined' || attr.priv === null) &&
Expand Down Expand Up @@ -61,15 +58,18 @@ const PKCS11_ECDSA_KEY = class extends Key {
*/
this._ski = attr.ski;
this._size = size;
this._isPrivate = false;

/*
* private key: ski set, ecpt set, priv set, pub set
* public key: ski set, ecpt set, priv unset, pub set
*/
if (typeof attr.priv !== 'undefined' && attr.priv !== null) {
this._handle = attr.priv;
this._pub = new PKCS11_ECDSA_KEY(
{ski: attr.ski, ecpt: attr.ecpt, pub: attr.pub}, size);
this._isPrivate = true;
if (attr.ski && attr.ecpt && attr.pub) {
this._pub = new Pkcs11EcdsaKey({ski: attr.ski, ecpt: attr.ecpt, pub: attr.pub}, size);
}
} else {
this._ecpt = attr.ecpt;
this._handle = attr.pub;
Expand Down Expand Up @@ -156,6 +156,10 @@ const PKCS11_ECDSA_KEY = class extends Key {
return this._ski.toString('hex');
}

getHandle() {
return this._handle.toString('hex');
}

isSymmetric() {
return false;
}
Expand All @@ -168,13 +172,20 @@ const PKCS11_ECDSA_KEY = class extends Key {
return this._pub === null ? this : this._pub;
}

/**
* Returns the string representation of the bytes that represent
* the key.
* if private key then it will be the handle to the private key
* if public key then it will be the actual public key
* @returns {string} key or handle to the key as string
*/
toBytes() {
if (this._pub !== null) {
throw new Error('toBytes: not allowed for private key');
if (this.isPrivate) {
return this._handle.toString('hex');
}

return this._ecpt;
return this._ecpt.toString('hex');
}
};

module.exports = PKCS11_ECDSA_KEY;
module.exports = Pkcs11EcdsaKey;
6 changes: 6 additions & 0 deletions fabric-common/test/Key.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ describe('Key', () => {
});
});

describe('#getHandle', () => {
it('should return undefined', () => {
should.equal(key.getHandle(), undefined);
});
});

describe('#isSymmetric', () => {
it('should return undefined', () => {
should.equal(key.isSymmetric(), undefined);
Expand Down
Loading

0 comments on commit cfbfd24

Please sign in to comment.