Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PKCS11 interface for HSM integration into DNSSEC ownership proof signing #30

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions PROVE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# bns-prove

### Generate DNSSEC ownership proofs using local keys or hardware signing module (HSM).

_What is a DNSSEC ownership proof?_

The proof is a chain of DNSSEC keys and signatures starting with the root zone
(ICANN KSK2017) and ending with a signed TXT record in the target zone
([example](test/data/test-claim.proof)). To claim a reserved name in the
[Handshake blockchain network](https://handshake.org),
a cryptocurrency wallet address is encoded into a TXT record which is then signed
and passed to the network for decentralized verification of ownership.

Usage:

```
bns-prove [options] domain-name txt-string
```

To prove ownership of the domain name `example.com`, pass `bns-prove` the path
to the directory containing the zone signing key (ZSK). The private key must
exist in the specified directory in BIND’s private key format (v1.3)
([example](test/data/Khns-claim-test-2.xyz.+008+27259.private))

```
$ bns-prove -b -K /path/to/DNSSEC/keys/ example.com hns-regtest:aakgpzi7wgivq75...
```

### Options

`-x`: Output the proof formatted as a hex string (deprecated).

`-b`: Output the proof formatted as a base64 string (expected by [Handshake full node API](https://hsd-dev.org/api-docs/#sendrawclaim)).

`-s`: Do not upgrade insecure algorithms like `RSASHA1` or `RSASHA1NSEC3SHA1` if the signature
indicates a stronger algorithm (like `RSASHA256`).

`-r`: Do not allow weak keys like `RSA-1024`

`-t <number>`: Specify the expiration time in seconds for the RRSIG (default is one year).

`-K <string>`: Specify the directory containing the expected DNSSEC private key.

### HSM Mode Options

This software has been tested with
[SoftHSMv2](https://github.com/opendnssec/SoftHSMv2)
and should operate correctly with any device with a PKCS#11 interface.

`--hsm-module <string>`: Path to the HSM manufacturer's PKCS11 library.

`--hsm-slot <number>`: HSM slot where expected DNSSEC private key can be found.

`--hsm-pin <string>`: Normal user PIN for logging in to specified slot.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
DNS library, server, and validating recursive resolver for node.js, in pure
javascript.

To use this package for DNSSEC-ownership proofs (required for reserved name
claims on the [Handshake blockchain network](https://handshake.org)) see the
[bns-prove guide](PROVE.md).

## Server Usage

### Base Server
Expand Down
73 changes: 66 additions & 7 deletions bin/bns-prove
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const Resolver = require('../lib/resolver/stub');
const Ownership = require('../lib/ownership');
const util = require('../lib/util');
const wire = require('../lib/wire');
const hsm = require('../lib/hsm');

const {
keyFlags,
Expand All @@ -37,6 +38,10 @@ let dir = '.';
let name = null;
let txt = null;

let hsmModule = null;
let hsmSlot = null;
let hsmPin = null;

for (let i = 2; i < process.argv.length; i++) {
const arg = process.argv[i];
const next = i !== process.argv.length - 1
Expand Down Expand Up @@ -65,7 +70,7 @@ for (let i = 2; i < process.argv.length; i++) {
}

case '-t': {
lifespan = util.parseU32(lifespan);
lifespan = util.parseU32(next);
i += 1;
break;
}
Expand All @@ -80,6 +85,30 @@ for (let i = 2; i < process.argv.length; i++) {
break;
}

case '--hsm-module': {
if (!next)
throw new Error('Invalid module path.');

hsmModule = next;
i += 1;
break;
}

case '--hsm-slot': {
hsmSlot = util.parseU32(next);
i += 1;
break;
}

case '--hsm-pin': {
if (!next)
throw new Error('Invalid pin.');

hsmPin = next;
i += 1;
break;
}

case '-h':
case '--help':
case '-?':
Expand All @@ -102,6 +131,13 @@ for (let i = 2; i < process.argv.length; i++) {
}
}

if ((hsmModule || hsmPin || hsmSlot) &&
(!hsmModule || !hsmPin || !hsmSlot)) {
throw new Error(
'HSM mode requires all three options: --hsm-module --hsm-slot --hsm-pin'
);
}

if (!name || name === '.')
throw new Error('No name provided.');

Expand Down Expand Up @@ -141,11 +177,6 @@ if (!txt)
if (!ctx.verifyKey(key, hardened))
continue;

const priv = await dnssec.readPrivateAsync(dir, key);

if (!priv)
continue;

const rr = new Record();
const rd = new TXTRecord();

Expand All @@ -157,7 +188,35 @@ if (!txt)

rd.txt.push(txt);

const sig = dnssec.sign(key, priv, [rr], lifespan);
console.log('Searching for key: ', key.data.keyTag());

let sig;
if (!hsmModule) {
// Signing with local key
const priv = await dnssec.readPrivateAsync(dir, key);

if (!priv)
continue;

sig = dnssec.sign(key, priv, [rr], lifespan);
} else {
let user;
try {
// Signing with HSM
user = new hsm.HSMUser({
module: hsmModule,
slot: hsmSlot,
pin: hsmPin
});
user.open();
sig = user.sign(key, [rr]);
} catch (e) {
console.log(e.message);
continue;
} finally {
user.close();
}
}

zone.claim.push(rr);
zone.claim.push(sig);
Expand Down
16 changes: 16 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,21 @@ const algHashes = {
[algs.ED448]: null
};

/**
* PKCS1v1.5+ASN.1 DigestInfo prefixes. (copied from bcrypto rsa.js)
* @see [RFC8017] Page 45, Section 9.2.
* @see [RFC8017] Page 63, Section B.1.
* @const {Object}
*/

const hashPrefixes = {
[hashes.SHA1]: Buffer.from('3021300906052b0e03021a05000414', 'hex'),
[hashes.SHA256]: Buffer.from('3031300d060960864801650304020105000420', 'hex'),
[hashes.GOST94]: Buffer.from('302e300a06062a850302021405000420', 'hex'),
[hashes.SHA384]: Buffer.from('3041300d060960864801650304020205000430', 'hex'),
[hashes.SHA512]: Buffer.from('3051300d060960864801650304020305000440', 'hex')
};

/**
* NSEC3 hashes.
* @enum {Number}
Expand Down Expand Up @@ -1170,6 +1185,7 @@ exports.algsByVal = algsByVal;
exports.hashes = hashes;
exports.hashesByVal = hashesByVal;
exports.algHashes = algHashes;
exports.hashPrefixes = hashPrefixes;
exports.nsecHashes = nsecHashes;
exports.nsecHashesByVal = nsecHashesByVal;
exports.certTypes = certTypes;
Expand Down
Loading