mirror of
https://github.com/arx-research/libhalo.git
synced 2026-01-09 21:28:02 -05:00
Implement gen_key and gen_key_confirm commands (#11)
This commit is contained in:
committed by
GitHub
parent
2dcf101004
commit
ef0924c62c
@@ -55,5 +55,7 @@ This library supports the following HaLo tag commands:
|
||||
* `sign_random` - sign a sequential counter with random pad using ECDSA private key on the NFC tag;
|
||||
* `write_latch` - write one-time programmable memory slot on the NFC tag;
|
||||
* `cfg_ndef` - configure the parameters returned in the dynamic URL when the NFC tag is scanned;
|
||||
* `gen_key` - request generation of the key #3 on the NFC tag;
|
||||
* `gen_key_confirm` - confirm the generated public key of key slot #3;
|
||||
|
||||
Full article: [Documentation of the available commands (HaLo Command Set)](/docs/halo-command-set.md)
|
||||
|
||||
10
cli/args.js
10
cli/args.js
@@ -3,7 +3,7 @@ const {ArgumentParser} = require("argparse");
|
||||
const parser = new ArgumentParser({
|
||||
description: 'HaLo - Command Line Tool for PC/SC'
|
||||
});
|
||||
const subparsers = parser.add_subparsers({help: 'command', dest: 'command'});
|
||||
const subparsers = parser.add_subparsers({help: 'command', dest: 'name'});
|
||||
|
||||
subparsers.add_parser("version", {help: "Get tag version."});
|
||||
|
||||
@@ -111,10 +111,16 @@ setNDEFCfgParser.add_argument("--flag-legacy-static", {
|
||||
required: false
|
||||
});
|
||||
|
||||
let genKeyParser = subparsers.add_parser("gen_key", {help: "Generate key in slot #3."});
|
||||
genKeyParser.add_argument("--entropy", {dest: 'entropy', help: "Additional entropy (32 bytes, hex encoded). Optional."});
|
||||
|
||||
let genKeyConfirmParser = subparsers.add_parser("gen_key_confirm", {help: "Confirm public key in slot #3 (only if additional entropy was provided)."});
|
||||
genKeyConfirmParser.add_argument("--public-key", {dest: 'publicKey', help: "Key slot #3 public key", required: true});
|
||||
|
||||
function parseArgs() {
|
||||
let args = parser.parse_args();
|
||||
|
||||
if (!args.command) {
|
||||
if (!args.name) {
|
||||
parser.print_help();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ Response:
|
||||
Command:
|
||||
```json
|
||||
{
|
||||
"command": "sign_raw",
|
||||
"name": "sign_raw",
|
||||
"keyNo": 1,
|
||||
"digest": "0102030401020304010203040102030401020304010203040102030401020304"
|
||||
}
|
||||
@@ -130,7 +130,7 @@ of the digest that will be signed.
|
||||
Command:
|
||||
```json
|
||||
{
|
||||
"command": "sign_random",
|
||||
"name": "sign_random",
|
||||
"keyNo": 2
|
||||
}
|
||||
```
|
||||
@@ -166,7 +166,7 @@ An object with `status: "ok"` key if the operation was successful.
|
||||
Command:
|
||||
```json
|
||||
{
|
||||
"command": "write_latch",
|
||||
"name": "write_latch",
|
||||
"latchNo": 1,
|
||||
"data": "0102030401020304010203040102030401020304010203040102030401020304"
|
||||
}
|
||||
@@ -213,7 +213,7 @@ An object with `status: "ok"` key if the operation was successful.
|
||||
Command:
|
||||
```json
|
||||
{
|
||||
"command": "cfg_ndef",
|
||||
"name": "cfg_ndef",
|
||||
"flagUseText": false,
|
||||
"flagHidePk1": false,
|
||||
"flagHidePk2": false,
|
||||
@@ -235,3 +235,73 @@ Response:
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
## Command: gen_key
|
||||
|
||||
Request the card to generate the key in #3 slot, which is uninitialized by default. You can optionally provide extra entropy for the key generation process using `command.entropy` key.
|
||||
|
||||
**Note:** If you provide additional entropy, this command will return the derived public key. You will need to confirm the public key using `gen_key_confirm` command.
|
||||
This step is not necessary if you don't provide additional entropy.
|
||||
|
||||
### Arguments
|
||||
|
||||
* `entropy` (str) - optional, additional entropy (32 bytes, hex encoded);
|
||||
|
||||
### Return value
|
||||
|
||||
* `status` (str) - `ok` when the key is generated successfully;
|
||||
* `publicKey` (str) - the public key that was generated;
|
||||
* `needsConfirm` (bool) - whether you need to call `gen_key_confrm` after running this command;
|
||||
|
||||
### Examples
|
||||
Command:
|
||||
```json
|
||||
{
|
||||
"name": "gen_key",
|
||||
"entropy": "3c825af7d2e1b02b6a00c257ebe883260b4aa6302c9878d412046d10141b261d"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"publicKey": "04f670a3d30e2b98b2e3691908722e643791be4a58eaf63e02026df7d67ae456cdece3e27671a96a104c50f6184cdc548b13a9fa3cc7a5c96956339256681a426d",
|
||||
"needsConfirm": true
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
* `ERROR_CODE_KEY_ALREADY_EXISTS` - the key in slot #3 already exists;
|
||||
|
||||
## Command: gen_key_confirm
|
||||
|
||||
Confirm the generated public key in slot #3. This call is only necessary if you have called `gen_key` with additional entropy.
|
||||
|
||||
### Arguments
|
||||
|
||||
* `publicKey` (str) - the public key returned from `gen_key` command;
|
||||
|
||||
### Return value
|
||||
|
||||
* `status` (str) - `ok` when the key generation is confirmed successfully;
|
||||
|
||||
### Examples
|
||||
Command:
|
||||
```json
|
||||
{
|
||||
"name": "gen_key_confirm",
|
||||
"publicKey": "04f670a3d30e2b98b2e3691908722e643791be4a58eaf63e02026df7d67ae456cdece3e27671a96a104c50f6184cdc548b13a9fa3cc7a5c96956339256681a426d"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
* `ERROR_CODE_KEY_ALREADY_EXISTS` - the key in slot #3 already exists;
|
||||
* `ERROR_CODE_CRYPTO_ERROR` - the `command.publicKey` that you have provided doesn't match the public key from the previous step or there was an internal failure with the key generator;
|
||||
|
||||
@@ -5,7 +5,7 @@ const {
|
||||
NFCMethodNotSupported,
|
||||
HaloLogicError
|
||||
} = require("../halo/exceptions");
|
||||
const {cmdSign, cmdCfgNDEF, cmdWriteLatch, cmdSignRandom} = require("../halo/commands");
|
||||
const {cmdSign, cmdCfgNDEF, cmdWriteLatch, cmdSignRandom, cmdGenKey, cmdGenKeyConfirm} = require("../halo/commands");
|
||||
|
||||
let isCallRunning = null;
|
||||
|
||||
@@ -35,6 +35,10 @@ async function execHaloCmd(command, options) {
|
||||
return await cmdWriteLatch(options, command);
|
||||
case 'cfg_ndef':
|
||||
return await cmdCfgNDEF(options, command);
|
||||
case 'gen_key':
|
||||
return await cmdGenKey(options, command);
|
||||
case 'gen_key_confirm':
|
||||
return await cmdGenKeyConfirm(options, command);
|
||||
default:
|
||||
throw new HaloLogicError("Unsupported command.name parameter specified.");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const {ERROR_CODES} = require("../halo/errors");
|
||||
const {readNDEF} = require("./pcsc_ndef");
|
||||
const {cmdSign, cmdWriteLatch, cmdCfgNDEF, cmdSignRandom} = require("../halo/commands");
|
||||
const {HaloLogicError, HaloTagError} = require("../halo/exceptions");
|
||||
const {execHaloCmd} = require("./common");
|
||||
|
||||
async function transceive(reader, command, options) {
|
||||
options = options || {};
|
||||
@@ -80,7 +80,7 @@ function makeOptions(reader) {
|
||||
}
|
||||
}
|
||||
|
||||
async function execHaloCmdPCSC(args, reader) {
|
||||
async function execHaloCmdPCSC(command, reader) {
|
||||
let version = await getVersion(reader);
|
||||
|
||||
let [verMajor, verMinor, verSeq, verShortId] = version.split('.');
|
||||
@@ -93,24 +93,10 @@ async function execHaloCmdPCSC(args, reader) {
|
||||
|
||||
await selectCore(reader);
|
||||
let options = makeOptions(reader);
|
||||
command = {...command};
|
||||
|
||||
try {
|
||||
switch (args.command) {
|
||||
case 'version':
|
||||
return version;
|
||||
case 'sign':
|
||||
return await cmdSign(options, args);
|
||||
case 'sign_raw':
|
||||
return await cmdSign(options, args);
|
||||
case 'sign_random':
|
||||
return await cmdSignRandom(options, args);
|
||||
case 'write_latch':
|
||||
return await cmdWriteLatch(options, args);
|
||||
case 'cfg_ndef':
|
||||
return await cmdCfgNDEF(options, args);
|
||||
default:
|
||||
throw new Error("Unsupported command");
|
||||
}
|
||||
return await execHaloCmd(command, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
const Buffer = require('buffer/').Buffer;
|
||||
const ethers = require('ethers');
|
||||
const {HaloLogicError, HaloTagError} = require("./exceptions");
|
||||
const {parseStatic, reformatSignature} = require("./utils");
|
||||
const {parseStatic, reformatSignature, mode, parseSig} = require("./utils");
|
||||
const {FLAGS} = require("./flags");
|
||||
const {sha256} = require("js-sha256");
|
||||
const EC = require("elliptic").ec;
|
||||
|
||||
const ec = new EC('secp256k1');
|
||||
|
||||
function extractPublicKeyWebNFC(keyNo, resp) {
|
||||
let publicKey = null;
|
||||
@@ -164,4 +168,75 @@ async function cmdCfgNDEF(options, args) {
|
||||
return {"status": "ok"};
|
||||
}
|
||||
|
||||
module.exports = {cmdSign, cmdSignRandom, cmdWriteLatch, cmdCfgNDEF};
|
||||
async function cmdGenKey(options, args) {
|
||||
if (!args.entropy) {
|
||||
let payload = Buffer.concat([
|
||||
Buffer.from("03", "hex")
|
||||
]);
|
||||
let resp = await options.exec(payload);
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
|
||||
return {"status": "ok", "publicKey": res.toString('hex'), "needsConfirm": false};
|
||||
} else {
|
||||
let entropyBuf = Buffer.from(args.entropy, "hex");
|
||||
|
||||
if (entropyBuf.length !== 32) {
|
||||
throw new HaloLogicError("The command.entropy should be exactly 32 bytes, hex encoded.");
|
||||
}
|
||||
|
||||
let payload = Buffer.concat([
|
||||
Buffer.from("03", "hex"),
|
||||
entropyBuf
|
||||
]);
|
||||
|
||||
let resp;
|
||||
|
||||
try {
|
||||
resp = await options.exec(payload);
|
||||
} catch (e) {
|
||||
if (e instanceof HaloTagError) {
|
||||
if (e.name === "ERROR_CODE_INVALID_LENGTH") {
|
||||
throw new HaloLogicError("The hardened key generation algorithm is not supported with this tag version.");
|
||||
}
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
|
||||
if (res.length === 65) {
|
||||
throw new HaloLogicError("The hardened key generation algorithm is not supported with this tag version.");
|
||||
}
|
||||
|
||||
let msg1 = Buffer.from(sha256(res.slice(0, 32)), 'hex');
|
||||
let msg2 = Buffer.from(sha256(res.slice(32, 64)), 'hex');
|
||||
let sig = res.slice(64);
|
||||
let sig1Length = sig[1];
|
||||
let sig1 = sig.slice(0, 2 + sig1Length);
|
||||
let sig2 = sig.slice(2 + sig1Length);
|
||||
|
||||
let candidates = [];
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
candidates.push(ec.recoverPubKey(msg1, parseSig(sig1), i).encode('hex'));
|
||||
candidates.push(ec.recoverPubKey(msg2, parseSig(sig2), i).encode('hex'));
|
||||
}
|
||||
|
||||
let bestPk = Buffer.from(mode(candidates), 'hex');
|
||||
return {"status": "ok", "publicKey": bestPk.toString('hex'), "needsConfirm": true};
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdGenKeyConfirm(options, args) {
|
||||
let payload = Buffer.concat([
|
||||
Buffer.from("09", "hex"),
|
||||
Buffer.from(args.publicKey, "hex")
|
||||
]);
|
||||
|
||||
await options.exec(payload);
|
||||
|
||||
return {"status": "ok"};
|
||||
}
|
||||
|
||||
module.exports = {cmdSign, cmdSignRandom, cmdWriteLatch, cmdCfgNDEF, cmdGenKey, cmdGenKeyConfirm};
|
||||
|
||||
@@ -41,27 +41,25 @@ function parseStatic(buffer) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function reformatSignature(digest, signature, publicKey) {
|
||||
signature = Buffer.from(signature, "hex");
|
||||
|
||||
if (signature[0] !== 0x30 || signature[2] !== 0x02) {
|
||||
throw new HaloLogicError("Invalid signature returned by the tag (2).");
|
||||
function parseSig(res) {
|
||||
if (res[0] !== 0x30 || res[2] !== 0x02) {
|
||||
throw new HaloLogicError("Unable to parse signature, unexpected header (1).");
|
||||
}
|
||||
|
||||
let rLen = signature[3];
|
||||
let rLen = res[3];
|
||||
|
||||
if (signature[rLen+4] !== 0x02) {
|
||||
throw new HaloLogicError("Invalid signature returned by the tag (3).");
|
||||
if (res[rLen + 4] !== 0x02) {
|
||||
throw new HaloLogicError("Unable to parse signature, unexpected header (2).");
|
||||
}
|
||||
|
||||
let sLen = signature[rLen+5];
|
||||
let sLen = res[rLen + 5];
|
||||
|
||||
if (signature.length !== rLen+4+2+sLen) {
|
||||
throw new HaloLogicError("Invalid signature returned by the tag (4).");
|
||||
if (res.length !== rLen + 4 + 2 + sLen) {
|
||||
throw new HaloLogicError("Unable to parse signature, unexpected length.");
|
||||
}
|
||||
|
||||
let r = signature.slice(4, rLen+4);
|
||||
let s = signature.slice(rLen+4+2, rLen+4+2+sLen);
|
||||
let r = res.slice(4, rLen + 4);
|
||||
let s = res.slice(rLen + 4 + 2, rLen + 4 + 2 + sLen);
|
||||
let rn = BigInt('0x' + r.toString('hex'));
|
||||
let sn = BigInt('0x' + s.toString('hex'));
|
||||
|
||||
@@ -74,7 +72,13 @@ function reformatSignature(digest, signature, publicKey) {
|
||||
sn = -sn + curveOrder;
|
||||
}
|
||||
|
||||
let fixedSig = {r: rn.toString(16), s: sn.toString(16)};
|
||||
return {r: rn.toString(16), s: sn.toString(16)};
|
||||
}
|
||||
|
||||
function reformatSignature(digest, signature, publicKey) {
|
||||
signature = Buffer.from(signature, "hex");
|
||||
let fixedSig = parseSig(signature);
|
||||
|
||||
let recoveryParam = null;
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
@@ -111,9 +115,18 @@ function reformatSignature(digest, signature, publicKey) {
|
||||
};
|
||||
}
|
||||
|
||||
function mode(arr) {
|
||||
return arr.sort((a, b) =>
|
||||
arr.filter(v => v === a).length
|
||||
- arr.filter(v => v === b).length
|
||||
).pop();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hex2arr,
|
||||
arr2hex,
|
||||
parseStatic,
|
||||
reformatSignature
|
||||
parseSig,
|
||||
reformatSignature,
|
||||
mode
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user