Skip to content

Commit 8f1a085

Browse files
committed
feature: allow provision of signing secret key instead of seed. Generate and store in config a new keypair if one is not provided. Every item create/update is signed.
1 parent 4edf00d commit 8f1a085

4 files changed

Lines changed: 88 additions & 38 deletions

File tree

src/cli.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,16 @@ const cmd = new Command()
7474
},
7575
)
7676
.globalEnv(
77-
"TRUESTAMP_SIGNING_KEY_SEED=<signingKeySeed:string>",
78-
"A Base64 encoded 32 byte random seed used to create an ed25519 signing keypair.",
77+
"TRUESTAMP_SIGNING_SECRET_KEY=<signingSecretKey:string>",
78+
"A Base64 encoded ed25519 secret key.",
7979
{
8080
required: false,
8181
prefix: "TRUESTAMP_",
8282
},
8383
)
8484
.globalOption(
85-
"-S, --signing-key-seed <signingKeySeed:string>",
86-
"A Base64 encoded 32 byte random seed used to create an ed25519 signing keypair. Overrides 'TRUESTAMP_SIGNING_KEY_SEED' env var.",
85+
"-S, --signing-secret-key <signingSecretKey:string>",
86+
"A Base64 encoded ed25519 secret key. Overrides 'TRUESTAMP_SIGNING_SECRET_KEY' env var.",
8787
)
8888
.action(() => {
8989
cmd.showHelp();

src/commands/items.ts

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "../deps.ts";
1111

1212
import {
13+
getSigningSecretKeyForEnv,
1314
logSelectedOutputFormat,
1415
signItemData,
1516
throwApiError,
@@ -34,7 +35,7 @@ const itemsCreate = new Command<{
3435
env: typeof environmentType;
3536
apiKey?: string;
3637
output: typeof outputType;
37-
signingKeySeed?: string;
38+
signingSecretKey?: string;
3839
}>()
3940
.description(
4041
`Create a new Item.
@@ -293,15 +294,18 @@ Pipe JSON content to the 'items create' command using '--input json' plus the '-
293294
skipOE: !options.observableEntropy,
294295
};
295296

297+
const signingSecretKeyFromConfig = getSigningSecretKeyForEnv(options.env);
298+
296299
if (jsonItem) {
297300
const itemRequest: ItemRequest = ItemRequestSchema.parse(jsonItem);
298301

299-
itemRequest.itemDataSignatures = options.signingKeySeed
300-
? signItemData(itemRequest, options.signingKeySeed)
302+
// Signing Secret Key provided as an option or env var overrides a key stored in the config file.
303+
itemRequest.itemDataSignatures = options.signingSecretKey
304+
? signItemData(itemRequest, options.signingSecretKey)
305+
: signingSecretKeyFromConfig
306+
? signItemData(itemRequest, signingSecretKeyFromConfig)
301307
: undefined;
302308

303-
console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
304-
305309
itemResp = await truestamp.createItem(itemRequest, createItemArgs);
306310
} else if (options.hash && options.hashType) {
307311
const itemRequest: ItemRequest = {
@@ -313,12 +317,13 @@ Pipe JSON content to the 'items create' command using '--input json' plus the '-
313317
],
314318
};
315319

316-
itemRequest.itemDataSignatures = options.signingKeySeed
317-
? signItemData(itemRequest, options.signingKeySeed)
320+
// Signing Secret Key provided as an option or env var overrides a key stored in the config file.
321+
itemRequest.itemDataSignatures = options.signingSecretKey
322+
? signItemData(itemRequest, options.signingSecretKey)
323+
: signingSecretKeyFromConfig
324+
? signItemData(itemRequest, signingSecretKeyFromConfig)
318325
: undefined;
319326

320-
// console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
321-
322327
itemResp = await truestamp.createItem(
323328
itemRequest,
324329
createItemArgs,
@@ -333,12 +338,13 @@ Pipe JSON content to the 'items create' command using '--input json' plus the '-
333338
],
334339
};
335340

336-
itemRequest.itemDataSignatures = options.signingKeySeed
337-
? signItemData(itemRequest, options.signingKeySeed)
341+
// Signing Secret Key provided as an option or env var overrides a key stored in the config file.
342+
itemRequest.itemDataSignatures = options.signingSecretKey
343+
? signItemData(itemRequest, options.signingSecretKey)
344+
: signingSecretKeyFromConfig
345+
? signItemData(itemRequest, signingSecretKeyFromConfig)
338346
: undefined;
339347

340-
// console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
341-
342348
itemResp = await truestamp.createItem(
343349
itemRequest,
344350
createItemArgs,
@@ -396,7 +402,7 @@ const itemsUpdate = new Command<{
396402
env: typeof environmentType;
397403
apiKey?: string;
398404
output: typeof outputType;
399-
signingKeySeed?: string;
405+
signingSecretKey?: string;
400406
}>()
401407
.description(
402408
`Update an existing Item by replacing it with a new one.
@@ -602,15 +608,18 @@ Pipe JSON content to the 'items update' command using '--input json' plus the '-
602608
skipOE: !options.observableEntropy,
603609
};
604610

611+
const signingSecretKeyFromConfig = getSigningSecretKeyForEnv(options.env);
612+
605613
if (jsonItem) {
606614
const itemRequest: ItemRequest = ItemRequestSchema.parse(jsonItem);
607615

608-
itemRequest.itemDataSignatures = options.signingKeySeed
609-
? signItemData(itemRequest, options.signingKeySeed)
616+
// Signing Secret Key provided as an option or env var overrides a key stored in the config file.
617+
itemRequest.itemDataSignatures = options.signingSecretKey
618+
? signItemData(itemRequest, options.signingSecretKey)
619+
: signingSecretKeyFromConfig
620+
? signItemData(itemRequest, signingSecretKeyFromConfig)
610621
: undefined;
611622

612-
console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
613-
614623
itemResp = await truestamp.updateItem(
615624
options.id,
616625
itemRequest,
@@ -626,12 +635,13 @@ Pipe JSON content to the 'items update' command using '--input json' plus the '-
626635
],
627636
};
628637

629-
itemRequest.itemDataSignatures = options.signingKeySeed
630-
? signItemData(itemRequest, options.signingKeySeed)
638+
// Signing Secret Key provided as an option or env var overrides a key stored in the config file.
639+
itemRequest.itemDataSignatures = options.signingSecretKey
640+
? signItemData(itemRequest, options.signingSecretKey)
641+
: signingSecretKeyFromConfig
642+
? signItemData(itemRequest, signingSecretKeyFromConfig)
631643
: undefined;
632644

633-
// console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
634-
635645
itemResp = await truestamp.updateItem(
636646
options.id,
637647
itemRequest,
@@ -647,12 +657,13 @@ Pipe JSON content to the 'items update' command using '--input json' plus the '-
647657
],
648658
};
649659

650-
itemRequest.itemDataSignatures = options.signingKeySeed
651-
? signItemData(itemRequest, options.signingKeySeed)
660+
// Signing Secret Key provided as an option or env var overrides a key stored in the config file.
661+
itemRequest.itemDataSignatures = options.signingSecretKey
662+
? signItemData(itemRequest, options.signingSecretKey)
663+
: signingSecretKeyFromConfig
664+
? signItemData(itemRequest, signingSecretKeyFromConfig)
652665
: undefined;
653666

654-
// console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
655-
656667
itemResp = await truestamp.updateItem(
657668
options.id,
658669
itemRequest,
@@ -680,7 +691,7 @@ export const items = new Command<{
680691
env: typeof environmentType;
681692
apiKey?: string;
682693
output: typeof outputType;
683-
signingKeySeed?: string;
694+
signingSecretKey?: string;
684695
}>()
685696
.description("Create or update Items.")
686697
.action(() => {

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import { Conf, Json } from "./deps.ts";
44

5+
// e.g. cat "/Users/glenn/Library/Preferences/com.truestamp.cli.development/config.json"
6+
57
function getConfigProjectNameForEnv(env: string): string {
68
return `com.truestamp.cli.${env}`;
79
}

src/utils.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Copyright © 2020-2022 Truestamp Inc. All rights reserved.
22

3-
import { generateKeyPairFromSeed, sign } from "@stablelib/ed25519";
3+
import {
4+
extractPublicKeyFromSecretKey,
5+
generateKeyPair,
6+
sign,
7+
} from "@stablelib/ed25519";
48
import { hash as sha256 } from "@stablelib/sha256";
59
import { z } from "zod";
610

@@ -11,6 +15,8 @@ import {
1115
encode as base64Encode,
1216
} from "@stablelib/base64";
1317

18+
import { getConfigKeyForEnv, setConfigKeyForEnv } from "./config.ts";
19+
1420
const OutputWrapper = z.object({
1521
text: z.string(),
1622
json: z.record(z.string().min(1), z.any()),
@@ -163,26 +169,57 @@ export async function put<T, U>(
163169

164170
export function signItemData(
165171
itemRequest: ItemRequest,
166-
signingKeySeed: Uint8Array | string,
172+
secretKey: Uint8Array | string,
167173
): Signature[] {
168-
if (typeof signingKeySeed === "string") {
169-
signingKeySeed = base64Decode(signingKeySeed);
174+
if (typeof secretKey === "string") {
175+
secretKey = base64Decode(secretKey);
170176
}
171177

172-
const signingKeyPair = generateKeyPairFromSeed(signingKeySeed);
178+
const publicKey = extractPublicKeyFromSecretKey(secretKey);
179+
173180
const canonicalData = canonify(itemRequest.itemData);
174181
const canonicalDataUint8Array = new TextEncoder().encode(canonicalData);
175182
const canonicalDataHash = sha256(canonicalDataUint8Array);
183+
176184
const canonicalDataHashSignature = sign(
177-
signingKeyPair.secretKey,
185+
secretKey,
178186
canonicalDataHash,
179187
);
180188

181189
return [
182190
{
183-
publicKey: base64Encode(signingKeyPair.publicKey),
191+
publicKey: base64Encode(publicKey),
184192
signature: base64Encode(canonicalDataHashSignature),
185193
signatureType: "ed25519",
186194
},
187195
];
188196
}
197+
198+
export function getSigningSecretKeyForEnv(env: string): Uint8Array {
199+
const configSecretKeyPropertyName = "signing_secret_key";
200+
const configPublicKeyPropertyName = "signing_public_key";
201+
202+
const secretKey = getConfigKeyForEnv(
203+
env,
204+
configSecretKeyPropertyName,
205+
);
206+
207+
if (secretKey) {
208+
return base64Decode(secretKey as string);
209+
} else {
210+
// Initialize and save a new KeyPair to config
211+
const keyPair = generateKeyPair();
212+
setConfigKeyForEnv(
213+
env,
214+
configSecretKeyPropertyName,
215+
base64Encode(keyPair.secretKey),
216+
);
217+
setConfigKeyForEnv(
218+
env,
219+
configPublicKeyPropertyName,
220+
base64Encode(keyPair.publicKey),
221+
);
222+
223+
return keyPair.secretKey;
224+
}
225+
}

0 commit comments

Comments
 (0)