diff --git a/cli/src.ts/args_cli.ts b/cli/src.ts/args_cli.ts index 0d5e813..7eb4c1d 100644 --- a/cli/src.ts/args_cli.ts +++ b/cli/src.ts/args_cli.ts @@ -8,6 +8,107 @@ import {ArgumentParser} from "argparse"; import {JSONParseAction} from "./actions.js"; import {printVersionInfo} from "./version.js"; +function configureNDEFCFGParserArgs(parser: ArgumentParser): ArgumentParser { + parser.add_argument("--flag-use-text", { + dest: "flagUseText", + help: "Use text NDEF record instead of the URL record.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-hide-pk1", { + dest: "flagHidePk1", + help: "Hide public key #1 in the dynamic URL.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-hide-pk2", { + dest: "flagHidePk2", + help: "Hide public key #2 in the dynamic URL.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-hide-pk3", { + dest: "flagHidePk3", + help: "Hide public key #3 in the dynamic URL (if it's generated).", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-show-pk1-attest", { + dest: "flagShowPk1Attest", + help: "Display public key #1 attest signature in the dynamic URL.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-show-pk2-attest", { + dest: "flagShowPk2Attest", + help: "Display public key #2 attest signature in the dynamic URL.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-show-pk3-attest", { + dest: "flagShowPk3Attest", + help: "Display public key #3 attest signature in the dynamic URL (if it's generated).", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-show-latch1-sig", { + dest: "flagShowLatch1Sig", + help: "Display the signature of latch #1 (if latch is set).", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-show-latch2-sig", { + dest: "flagShowLatch2Sig", + help: "Display the signature of latch #2 (if latch is set).", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-hide-rndsig", { + dest: "flagHideRNDSIG", + help: "Hide \"rnd\" and \"rndsig\" fields. The counter's signature will be generated only upon manual request.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-hide-cmdres", { + dest: "flagHideCMDRES", + help: "Hide \"cmd\" and \"res\" fields. With this flag set, it will be not possible to execute commands through WebNFC.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-legacy-static", { + dest: "flagLegacyStatic", + help: "Display public keys in the legacy format, using the \"static\" field with all keys concatenated together.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-show-pkn", { + dest: "flagShowPkN", + help: "Display the public key of the selected key slot in the URL.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-show-pkn-attest", { + dest: "flagShowPkNAttest", + help: "Display the attest of the public key of the selected key slot in the URL.", + action: 'store_true', + required: false + }); + parser.add_argument("--flag-rndsig-use-bjj62", { + dest: "flagRNDSIGUseBJJ62", + help: "Use BJJ key slot #62 for the \"rndsig\" signature.", + action: 'store_true', + required: false + }); + parser.add_argument("--pkn", { + dest: "pkN", + help: "Key slot number for --flag-show-pkn and --flag-show-pkn-attest", + type: 'int', + required: false + }); + + return parser; +} + const parser = new ArgumentParser({ description: 'HaLo - Command Line Tool for PC/SC' }); @@ -92,102 +193,7 @@ writeLatchParser.add_argument("-d", "--data", { }); const setNDEFCfgParser = subparsers.add_parser("cfg_ndef", {help: "Configure the tag's NDEF data."}); -setNDEFCfgParser.add_argument("--flag-use-text", { - dest: "flagUseText", - help: "Use text NDEF record instead of the URL record.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-hide-pk1", { - dest: "flagHidePk1", - help: "Hide public key #1 in the dynamic URL.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-hide-pk2", { - dest: "flagHidePk2", - help: "Hide public key #2 in the dynamic URL.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-hide-pk3", { - dest: "flagHidePk3", - help: "Hide public key #3 in the dynamic URL (if it's generated).", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-show-pk1-attest", { - dest: "flagShowPk1Attest", - help: "Display public key #1 attest signature in the dynamic URL.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-show-pk2-attest", { - dest: "flagShowPk2Attest", - help: "Display public key #2 attest signature in the dynamic URL.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-show-pk3-attest", { - dest: "flagShowPk3Attest", - help: "Display public key #3 attest signature in the dynamic URL (if it's generated).", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-show-latch1-sig", { - dest: "flagShowLatch1Sig", - help: "Display the signature of latch #1 (if latch is set).", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-show-latch2-sig", { - dest: "flagShowLatch2Sig", - help: "Display the signature of latch #2 (if latch is set).", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-hide-rndsig", { - dest: "flagHideRNDSIG", - help: "Hide \"rnd\" and \"rndsig\" fields. The counter's signature will be generated only upon manual request.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-hide-cmdres", { - dest: "flagHideCMDRES", - help: "Hide \"cmd\" and \"res\" fields. With this flag set, it will be not possible to execute commands through WebNFC.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-legacy-static", { - dest: "flagLegacyStatic", - help: "Display public keys in the legacy format, using the \"static\" field with all keys concatenated together.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-show-pkn", { - dest: "flagShowPkN", - help: "Display the public key of the selected key slot in the URL.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-show-pkn-attest", { - dest: "flagShowPkNAttest", - help: "Display the attest of the public key of the selected key slot in the URL.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--flag-rndsig-use-bjj62", { - dest: "flagRNDSIGUseBJJ62", - help: "Use BJJ key slot #62 for the \"rndsig\" signature.", - action: 'store_true', - required: false -}); -setNDEFCfgParser.add_argument("--pkn", { - dest: "pkN", - help: "Key slot number for --flag-show-pkn and --flag-show-pkn-attest", - type: 'int', - required: false -}); +configureNDEFCFGParserArgs(setNDEFCfgParser); const genKeyParser = subparsers.add_parser("gen_key", {help: "Perform the first step of the key generation procedure."}); genKeyParser.add_argument("-k", "--key-no", { @@ -377,6 +383,51 @@ storeGraffitiParser.add_argument("--data", { default: '' }); +const replacePasswordStoreGraffitiParser = subparsers.add_parser("replace_password_store_graffiti", {help: "Replace key slot password and store graffiti data to the tag."}); +replacePasswordStoreGraffitiParser.add_argument("-k", "--key-no", { + dest: 'keyNo', + 'default': 3, + type: 'int', + help: "Target key slot number (default: 3)." +}); +replacePasswordStoreGraffitiParser.add_argument("--cur-password", { + dest: 'currentPassword', + help: "Current slot password (utf-8 string).", + required: true +}); +replacePasswordStoreGraffitiParser.add_argument("--new-password", { + dest: 'newPassword', + help: "New slot password (utf-8 string).", + required: true +}); +replacePasswordStoreGraffitiParser.add_argument("-n", "--slot-no", { + dest: 'slotNo', + type: 'int', + help: "Target data slot number.", + required: true +}); +replacePasswordStoreGraffitiParser.add_argument("--data", { + dest: 'data', + help: "Data to be stored (ASCII string).", + required: true, + default: '' +}); + +const cfgNDEFStoreGraffitiParser = subparsers.add_parser("cfg_ndef_store_graffiti", {help: "Configure the tag's NDEF data and store graffiti data to the tag."}); +configureNDEFCFGParserArgs(cfgNDEFStoreGraffitiParser); +cfgNDEFStoreGraffitiParser.add_argument("-n", "--slot-no", { + dest: 'slotNo', + type: 'int', + help: "Target data slot number.", + required: true +}); +cfgNDEFStoreGraffitiParser.add_argument("--data", { + dest: 'data', + help: "Data to be stored (ASCII string).", + required: true, + default: '' +}); + subparsers.add_parser("pcsc_detect", {help: "Detect PC/SC readers and HaLo tags (for debugging)."}); const cfgSimParser = subparsers.add_parser("sim_cfg", {help: "Configure simulation."}); diff --git a/core/src.ts/drivers/common.ts b/core/src.ts/drivers/common.ts index ad8e37f..3585510 100644 --- a/core/src.ts/drivers/common.ts +++ b/core/src.ts/drivers/common.ts @@ -9,10 +9,30 @@ import { HaloTagError } from "../halo/exceptions.js"; import { - cmdGetPkeys, cmdSign, cmdCfgNDEF, cmdWriteLatch, cmdSignRandom, cmdGenKey, cmdGenKeyConfirm, cmdGenKeyFinalize, - cmdSignChallenge, cmdSetURLSubdomain, cmdSetPassword, cmdUnsetPassword, cmdReplacePassword, cmdGetKeyInfo, - cmdGetTransportPK, cmdLoadTransportPK, cmdExportKey, cmdImportKey, cmdImportKeyInit, cmdGetDataStruct, - cmdGetGraffiti, cmdStoreGraffiti + cmdGetPkeys, + cmdSign, + cmdCfgNDEF, + cmdWriteLatch, + cmdSignRandom, + cmdGenKey, + cmdGenKeyConfirm, + cmdGenKeyFinalize, + cmdSignChallenge, + cmdSetURLSubdomain, + cmdSetPassword, + cmdUnsetPassword, + cmdReplacePassword, + cmdGetKeyInfo, + cmdGetTransportPK, + cmdLoadTransportPK, + cmdExportKey, + cmdImportKey, + cmdImportKeyInit, + cmdGetDataStruct, + cmdGetGraffiti, + cmdStoreGraffiti, + cmdCfgNDEFStoreGraffiti, + cmdReplacePasswordStoreGraffiti } from "../halo/commands.js"; import {ERROR_CODES} from "../halo/errors.js"; import { @@ -73,6 +93,10 @@ async function execHaloCmd(command: HaloCommandObject, options: ExecHaloCmdOptio return await cmdGetGraffiti(options, command); case 'store_graffiti': return await cmdStoreGraffiti(options, command); + case 'replace_password_store_graffiti': + return await cmdReplacePasswordStoreGraffiti(options, command); + case 'cfg_ndef_store_graffiti': + return await cmdCfgNDEFStoreGraffiti(options, command); default: throw new HaloLogicError("Unsupported command.name parameter specified."); } diff --git a/core/src.ts/halo/cmdcodes.ts b/core/src.ts/halo/cmdcodes.ts index 6bab873..1bc32a2 100644 --- a/core/src.ts/halo/cmdcodes.ts +++ b/core/src.ts/halo/cmdcodes.ts @@ -30,6 +30,13 @@ const CMD_CODES = { "SHARED_CMD_SET_PASSWORD": 0xA3, "SHARED_CMD_UNSET_PASSWORD": 0xA4, "SHARED_CMD_REPLACE_PASSWORD": 0xA5, + "CRED_CMD_GET_TRANSPORT_PK_ATT": 0xA6, + "CRED_CMD_LOAD_TRANSPORT_PK": 0xA7, + "CRED_CMD_EXPORT_KEY": 0xA8, + "CRED_CMD_IMPORT_KEY_INIT": 0xA9, + "CRED_CMD_IMPORT_KEY": 0xAA, + "SHARED_CMD_REPLACE_PASSWORD_STORE_GRAFFITI": 0xAB, + // reserved, do not use "SHARED_CMD_RESERVED_2": 0xCC, @@ -40,12 +47,7 @@ const CMD_CODES = { "SHARED_CMD_GET_DATA_VERSION": 0xD7, "SHARED_CMD_GET_URL_SUBDOMAIN": 0xD6, "SHARED_CMD_SET_NDEF_MODE": 0xD8, - - "CRED_CMD_GET_TRANSPORT_PK_ATT": 0xA6, - "CRED_CMD_LOAD_TRANSPORT_PK": 0xA7, - "CRED_CMD_EXPORT_KEY": 0xA8, - "CRED_CMD_IMPORT_KEY_INIT": 0xA9, - "CRED_CMD_IMPORT_KEY": 0xAA, + "SHARED_CMD_SET_NDEF_MODE_STORE_GRAFFITI": 0xD9, }; export {CMD_CODES}; diff --git a/core/src.ts/halo/command_types.ts b/core/src.ts/halo/command_types.ts index c273780..2c865ac 100644 --- a/core/src.ts/halo/command_types.ts +++ b/core/src.ts/halo/command_types.ts @@ -1,4 +1,4 @@ -import {ASCIIString, HexString, KeyFlags, KeySlotNo, KeyState, PublicKeyList} from "../types.js"; +import {ASCIIString, HaloCmdCFGNDEF, HexString, KeyFlags, KeySlotNo, KeyState, PublicKeyList} from "../types.js"; import {TypedDataDomain, TypedDataField} from "ethers"; export interface HaloCmdGetPkeys {} @@ -223,3 +223,14 @@ export interface HaloCmdSetURLSubdomain { export interface HaloResSetURLSubdomain { status: "ok" } + +export type HaloCmdReplacePasswordStoreGraffiti = HaloCmdReplacePassword & HaloCmdStoreGraffiti; +export type HaloCmdCFGNDEFStoreGraffiti = HaloCmdCFGNDEF & HaloCmdStoreGraffiti; + +export interface HaloResReplacePasswordStoreGraffiti { + status: "ok" +} + +export interface HaloResCFGNDEFStoreGraffiti { + status: "ok" +} diff --git a/core/src.ts/halo/commands.ts b/core/src.ts/halo/commands.ts index 36c8155..d62526d 100644 --- a/core/src.ts/halo/commands.ts +++ b/core/src.ts/halo/commands.ts @@ -10,13 +10,13 @@ import {Buffer} from 'buffer/index.js'; import {ethers} from 'ethers'; import {HaloLogicError, HaloTagError} from "./exceptions.js"; import { + BJJ_ORDER, convertSignature, mode, - parseSig, parsePublicKeys, + parseSig, randomBuffer, SECP256k1_ORDER, - BJJ_ORDER, sigToDer } from "./util.js"; import {FLAGS} from "./flags.js"; @@ -27,35 +27,61 @@ import pbkdf2 from 'pbkdf2'; import {KEY_FLAGS, parseKeyFlags} from "./keyflags.js"; import { ExecHaloCmdOptions, - ExecReturnStruct, HaloCmdCFGNDEF, HaloCmdGenKey, HaloCmdGenKeyConfirm, HaloCmdGenKeyFinalize, - HaloResCFGNDEF, HaloResGenKey, HaloResGenKeyConfirm, HaloResGenKeyFinalize, KeyFlags, + ExecReturnStruct, + HaloCmdCFGNDEF, + HaloCmdCFGNDEFStoreGraffiti, + HaloCmdGenKey, + HaloCmdGenKeyConfirm, + HaloCmdGenKeyFinalize, + HaloCmdReplacePasswordStoreGraffiti, + HaloResCFGNDEF, + HaloResCFGNDEFStoreGraffiti, + HaloResGenKey, + HaloResGenKeyConfirm, + HaloResGenKeyFinalize, + HaloResReplacePasswordStoreGraffiti, + KeyFlags, PublicKeyList } from "../types.js"; import { HaloCmdExportKey, HaloCmdGetDataStruct, - HaloCmdGetGraffiti, HaloCmdGetKeyInfo, + HaloCmdGetGraffiti, + HaloCmdGetKeyInfo, HaloCmdGetPkeys, HaloCmdGetTransportPK, HaloCmdImportKey, - HaloCmdImportKeyInit, HaloCmdLoadTransportPK, HaloCmdReplacePassword, HaloCmdSetPassword, HaloCmdSetURLSubdomain, + HaloCmdImportKeyInit, + HaloCmdLoadTransportPK, + HaloCmdReplacePassword, + HaloCmdSetPassword, + HaloCmdSetURLSubdomain, HaloCmdSign, HaloCmdSignChallenge, HaloCmdSignRandom, - HaloCmdStoreGraffiti, HaloCmdUnsetPassword, + HaloCmdStoreGraffiti, + HaloCmdUnsetPassword, HaloCmdWriteLatch, HaloResExportKey, HaloResGetDataStruct, - HaloResGetGraffiti, HaloResGetKeyInfo, - HaloResGetPkeys, HaloResGetTransportPK, + HaloResGetGraffiti, + HaloResGetKeyInfo, + HaloResGetPkeys, + HaloResGetTransportPK, HaloResImportKey, HaloResImportKeyInit, - HaloResInputObj, HaloResLoadTransportPK, HaloResReplacePassword, HaloResSetPassword, HaloResSetURLSubdomain, + HaloResInputObj, + HaloResLoadTransportPK, + HaloResReplacePassword, + HaloResSetPassword, + HaloResSetURLSubdomain, HaloResSign, HaloResSignChallenge, HaloResSignRandom, - HaloResStoreGraffiti, HaloResUnsetPassword, - HaloResWriteLatch, StructErrorResponse + HaloResStoreGraffiti, + HaloResUnsetPassword, + HaloResWriteLatch, + StructErrorResponse } from "./command_types.js"; const ec = new elliptic.ec('secp256k1'); @@ -340,7 +366,7 @@ async function cmdSignChallenge(options: ExecHaloCmdOptions, args: HaloCmdSignCh }; } -async function cmdCfgNDEF(options: ExecHaloCmdOptions, args: HaloCmdCFGNDEF): Promise { +function prepareCfgNDEFInput(args: HaloCmdCFGNDEF): Buffer { if (args.flagHidePk1 && args.flagHidePk2) { throw new HaloLogicError("It's not allowed to use both flagHidePk1 and flagHidePk2."); } @@ -361,15 +387,19 @@ async function cmdCfgNDEF(options: ExecHaloCmdOptions, args: HaloCmdCFGNDEF): Pr flagBuf[2] = args.pkN; } - const payload = Buffer.concat([ + return Buffer.concat([ Buffer.from([CMD.SHARED_CMD_SET_NDEF_MODE]), flagBuf ]); +} + +async function cmdCfgNDEF(options: ExecHaloCmdOptions, args: HaloCmdCFGNDEF): Promise { + const payload = prepareCfgNDEFInput(args); await options.exec(payload); return { "status": "ok", - "cfgBytes": flagBuf.toString('hex').toUpperCase() + "cfgBytes": payload.slice(1).toString('hex').toUpperCase() }; } @@ -589,7 +619,7 @@ async function cmdUnsetPassword(options: ExecHaloCmdOptions, args: HaloCmdUnsetP return {"status": "ok"}; } -async function cmdReplacePassword(options: ExecHaloCmdOptions, args: HaloCmdReplacePassword): Promise { +function prepareReplacePasswordInput(args: HaloCmdReplacePassword): Buffer { const curPassword = pbkdf2.pbkdf2Sync(args.currentPassword, 'HaLoChipSalt', 5000, 16, 'sha512'); const newPassword = pbkdf2.pbkdf2Sync(args.newPassword, 'HaLoChipSalt', 5000, 16, 'sha512'); @@ -610,13 +640,16 @@ async function cmdReplacePassword(options: ExecHaloCmdOptions, args: HaloCmdRepl curPassword ])), "hex"); - const payload = Buffer.concat([ + return Buffer.concat([ Buffer.from([CMD.SHARED_CMD_REPLACE_PASSWORD]), Buffer.from([args.keyNo]), ct, authHash ]); +} +async function cmdReplacePassword(options: ExecHaloCmdOptions, args: HaloCmdReplacePassword): Promise { + const payload = prepareReplacePasswordInput(args); await options.exec(payload); return {"status": "ok"}; @@ -844,13 +877,16 @@ async function cmdGetGraffiti(options: ExecHaloCmdOptions, args: HaloCmdGetGraff } } -async function cmdStoreGraffiti(options: ExecHaloCmdOptions, args: HaloCmdStoreGraffiti): Promise { - const payload = Buffer.concat([ +function prepareStoreGraffitiInput(args: HaloCmdStoreGraffiti): Buffer { + return Buffer.concat([ Buffer.from([CMD.SHARED_CMD_STORE_GRAFFITI]), Buffer.from([args.slotNo]), Buffer.from(args.data, 'ascii') ]); +} +async function cmdStoreGraffiti(options: ExecHaloCmdOptions, args: HaloCmdStoreGraffiti): Promise { + const payload = prepareStoreGraffitiInput(args); await options.exec(payload); return { @@ -858,6 +894,36 @@ async function cmdStoreGraffiti(options: ExecHaloCmdOptions, args: HaloCmdStoreG } } +async function cmdReplacePasswordStoreGraffiti(options: ExecHaloCmdOptions, args: HaloCmdReplacePasswordStoreGraffiti): Promise { + const setPasswordInput = prepareReplacePasswordInput(args); + const storeGraffitiInput = prepareStoreGraffitiInput(args); + + await options.exec(Buffer.concat([ + Buffer.from([CMD.SHARED_CMD_REPLACE_PASSWORD_STORE_GRAFFITI]), + Buffer.from([setPasswordInput.length]), + setPasswordInput, + Buffer.from([storeGraffitiInput.length]), + storeGraffitiInput + ])); + + return {"status": "ok"}; +} + +async function cmdCfgNDEFStoreGraffiti(options: ExecHaloCmdOptions, args: HaloCmdCFGNDEFStoreGraffiti): Promise { + const cfgNDEFInput = prepareCfgNDEFInput(args); + const storeGraffitiInput = prepareStoreGraffitiInput(args); + + await options.exec(Buffer.concat([ + Buffer.from([CMD.SHARED_CMD_SET_NDEF_MODE_STORE_GRAFFITI]), + Buffer.from([cfgNDEFInput.length]), + cfgNDEFInput, + Buffer.from([storeGraffitiInput.length]), + storeGraffitiInput + ])); + + return {"status": "ok"}; +} + export { cmdSign, cmdSignRandom, @@ -881,6 +947,8 @@ export { cmdGetDataStruct, cmdGetGraffiti, cmdStoreGraffiti, + cmdReplacePasswordStoreGraffiti, + cmdCfgNDEFStoreGraffiti, }; export type * from "./command_types.js";