@attest-it/core 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -63,7 +63,7 @@ __export(crypto_exports, {
63
63
  verify: () => verify
64
64
  });
65
65
  async function runOpenSSL(args, stdin) {
66
- return new Promise((resolve4, reject) => {
66
+ return new Promise((resolve5, reject) => {
67
67
  const child = child_process.spawn("openssl", args, {
68
68
  stdio: ["pipe", "pipe", "pipe"]
69
69
  });
@@ -79,12 +79,15 @@ async function runOpenSSL(args, stdin) {
79
79
  reject(new Error(`Failed to spawn OpenSSL: ${err.message}`));
80
80
  });
81
81
  child.on("close", (code) => {
82
- resolve4({
82
+ resolve5({
83
83
  exitCode: code ?? 1,
84
84
  stdout: Buffer.concat(stdoutChunks),
85
85
  stderr
86
86
  });
87
87
  });
88
+ if (stdin) {
89
+ child.stdin.write(stdin);
90
+ }
88
91
  child.stdin.end();
89
92
  });
90
93
  }
@@ -157,7 +160,8 @@ async function generateKeyPair(options = {}) {
157
160
  const {
158
161
  privatePath = getDefaultPrivateKeyPath(),
159
162
  publicPath = getDefaultPublicKeyPath(),
160
- force = false
163
+ force = false,
164
+ passphrase
161
165
  } = options;
162
166
  const privateExists = await fileExists(privatePath);
163
167
  const publicExists = await fileExists(publicPath);
@@ -181,12 +185,25 @@ async function generateKeyPair(options = {}) {
181
185
  "-out",
182
186
  privatePath
183
187
  ];
184
- const genResult = await runOpenSSL(genArgs);
188
+ if (passphrase) {
189
+ genArgs.push("-aes256", "-pass", "stdin");
190
+ }
191
+ const genResult = await runOpenSSL(
192
+ genArgs,
193
+ passphrase ? Buffer.from(passphrase + "\n") : void 0
194
+ );
185
195
  if (genResult.exitCode !== 0) {
186
196
  throw new Error(`Failed to generate private key: ${genResult.stderr}`);
187
197
  }
188
198
  await setKeyPermissions(privatePath);
189
- const pubResult = await runOpenSSL(["pkey", "-in", privatePath, "-pubout", "-out", publicPath]);
199
+ const pubArgs = ["pkey", "-in", privatePath, "-pubout", "-out", publicPath];
200
+ if (passphrase) {
201
+ pubArgs.push("-passin", "stdin");
202
+ }
203
+ const pubResult = await runOpenSSL(
204
+ pubArgs,
205
+ passphrase ? Buffer.from(passphrase + "\n") : void 0
206
+ );
190
207
  if (pubResult.exitCode !== 0) {
191
208
  throw new Error(`Failed to extract public key: ${pubResult.stderr}`);
192
209
  }
@@ -201,7 +218,7 @@ async function generateKeyPair(options = {}) {
201
218
  }
202
219
  async function sign(options) {
203
220
  await ensureOpenSSLAvailable();
204
- const { privateKeyPath, keyProvider, keyRef, data } = options;
221
+ const { privateKeyPath, keyProvider, keyRef, data, passphrase } = options;
205
222
  let effectiveKeyPath;
206
223
  let cleanup;
207
224
  if (keyProvider && keyRef) {
@@ -226,9 +243,22 @@ async function sign(options) {
226
243
  const sigFile = path2__namespace.join(tmpDir, "sig.bin");
227
244
  try {
228
245
  await fs8__namespace.writeFile(dataFile, processBuffer);
229
- const signArgs = ["dgst", "-sha256", "-sign", effectiveKeyPath, "-out", sigFile, dataFile];
230
- const result = await runOpenSSL(signArgs);
246
+ const signArgs = ["dgst", "-sha256"];
247
+ if (passphrase) {
248
+ signArgs.push("-passin", "stdin");
249
+ }
250
+ signArgs.push("-sign", effectiveKeyPath, "-out", sigFile, dataFile);
251
+ const result = await runOpenSSL(
252
+ signArgs,
253
+ passphrase ? Buffer.from(passphrase + "\n") : void 0
254
+ );
231
255
  if (result.exitCode !== 0) {
256
+ const stderr = result.stderr.toLowerCase();
257
+ if (stderr.includes("bad decrypt") || stderr.includes("bad password") || stderr.includes("unable to load key") || stderr.includes("wrong password")) {
258
+ throw new Error(
259
+ "Failed to decrypt private key. Please check that the passphrase is correct."
260
+ );
261
+ }
232
262
  throw new Error(`Failed to sign data: ${result.stderr}`);
233
263
  }
234
264
  const sigBuffer = await fs8__namespace.readFile(sigFile);
@@ -305,7 +335,8 @@ var teamMemberSchema = zod.z.object({
305
335
  name: zod.z.string().min(1, "Team member name cannot be empty"),
306
336
  email: zod.z.string().email().optional(),
307
337
  github: zod.z.string().min(1).optional(),
308
- publicKey: zod.z.string().min(1, "Public key is required")
338
+ publicKey: zod.z.string().min(1, "Public key is required"),
339
+ publicKeyAlgorithm: zod.z.enum(["ed25519"]).optional()
309
340
  }).strict();
310
341
  var fingerprintConfigSchema = zod.z.object({
311
342
  paths: zod.z.array(zod.z.string().min(1, "Path cannot be empty")).min(1, "At least one path is required"),
@@ -335,6 +366,7 @@ var settingsSchema = zod.z.object({
335
366
  maxAgeDays: zod.z.number().int().positive().default(30),
336
367
  publicKeyPath: zod.z.string().default(".attest-it/pubkey.pem"),
337
368
  attestationsPath: zod.z.string().default(".attest-it/attestations.json"),
369
+ sealsPath: zod.z.string().default(".attest-it/seals.json"),
338
370
  defaultCommand: zod.z.string().optional(),
339
371
  keyProvider: keyProviderSchema.optional()
340
372
  // Note: algorithm field was removed - RSA is the only supported algorithm
@@ -488,7 +520,8 @@ function toAttestItConfig(config) {
488
520
  settings: {
489
521
  maxAgeDays: config.settings.maxAgeDays,
490
522
  publicKeyPath: config.settings.publicKeyPath,
491
- attestationsPath: config.settings.attestationsPath
523
+ attestationsPath: config.settings.attestationsPath,
524
+ sealsPath: config.settings.sealsPath
492
525
  },
493
526
  suites: {}
494
527
  };
@@ -534,7 +567,8 @@ var teamMemberSchema2 = zod.z.object({
534
567
  name: zod.z.string().min(1, "Team member name cannot be empty"),
535
568
  email: zod.z.string().email().optional(),
536
569
  github: zod.z.string().min(1).optional(),
537
- publicKey: zod.z.string().min(1, "Public key is required")
570
+ publicKey: zod.z.string().min(1, "Public key is required"),
571
+ publicKeyAlgorithm: zod.z.literal("ed25519").optional()
538
572
  }).strict();
539
573
  var fingerprintConfigSchema2 = zod.z.object({
540
574
  paths: zod.z.array(zod.z.string().min(1, "Path cannot be empty")).min(1, "At least one path is required"),
@@ -575,7 +609,8 @@ var keyProviderSchema2 = zod.z.object({
575
609
  var policySettingsSchema = zod.z.object({
576
610
  maxAgeDays: zod.z.number().int().positive().default(30),
577
611
  publicKeyPath: zod.z.string().default(".attest-it/pubkey.pem"),
578
- attestationsPath: zod.z.string().default(".attest-it/attestations.json")
612
+ attestationsPath: zod.z.string().default(".attest-it/attestations.json"),
613
+ sealsPath: zod.z.string().default(".attest-it/seals.json")
579
614
  }).strict();
580
615
  var policySchema = zod.z.object({
581
616
  version: zod.z.literal(1),
@@ -752,7 +787,8 @@ function mergeConfigs(policy, operational) {
752
787
  // Security settings from policy (these are trust-critical)
753
788
  maxAgeDays: policy.settings.maxAgeDays,
754
789
  publicKeyPath: policy.settings.publicKeyPath,
755
- attestationsPath: policy.settings.attestationsPath
790
+ attestationsPath: policy.settings.attestationsPath,
791
+ sealsPath: policy.settings.sealsPath
756
792
  };
757
793
  if (operational.settings.defaultCommand !== void 0) {
758
794
  settings.defaultCommand = operational.settings.defaultCommand;
@@ -847,7 +883,7 @@ function computeFinalFingerprint(fileHashes) {
847
883
  }
848
884
  async function hashFileAsync(realPath, normalizedPath, stats) {
849
885
  if (stats.size > LARGE_FILE_THRESHOLD) {
850
- return new Promise((resolve4, reject) => {
886
+ return new Promise((resolve5, reject) => {
851
887
  const hash2 = crypto3__namespace.createHash("sha256");
852
888
  hash2.update(normalizedPath);
853
889
  hash2.update(":");
@@ -856,7 +892,7 @@ async function hashFileAsync(realPath, normalizedPath, stats) {
856
892
  hash2.update(chunk);
857
893
  });
858
894
  stream.on("end", () => {
859
- resolve4(hash2.digest());
895
+ resolve5(hash2.digest());
860
896
  });
861
897
  stream.on("error", reject);
862
898
  });
@@ -1470,16 +1506,23 @@ var FilesystemKeyProvider = class {
1470
1506
  * @param options - Key generation options
1471
1507
  */
1472
1508
  async generateKeyPair(options) {
1473
- const { publicKeyPath, force = false } = options;
1474
- const result = await generateKeyPair({
1509
+ const { publicKeyPath, force = false, passphrase } = options;
1510
+ const cryptoOptions = {
1475
1511
  privatePath: this.privateKeyPath,
1476
1512
  publicPath: publicKeyPath,
1477
1513
  force
1478
- });
1514
+ };
1515
+ if (passphrase !== void 0) {
1516
+ cryptoOptions.passphrase = passphrase;
1517
+ }
1518
+ const result = await generateKeyPair(cryptoOptions);
1519
+ const encrypted = passphrase !== void 0 && passphrase.length > 0;
1520
+ const encryptionStatus = encrypted ? " (passphrase-encrypted)" : "";
1479
1521
  return {
1480
1522
  privateKeyRef: result.privatePath,
1481
1523
  publicKeyPath: result.publicPath,
1482
- storageDescription: `Filesystem: ${result.privatePath}`
1524
+ storageDescription: `Filesystem: ${result.privatePath}${encryptionStatus}`,
1525
+ encrypted
1483
1526
  };
1484
1527
  }
1485
1528
  /**
@@ -1718,7 +1761,7 @@ var OnePasswordKeyProvider = class _OnePasswordKeyProvider {
1718
1761
  }
1719
1762
  };
1720
1763
  async function execCommand(command, args) {
1721
- return new Promise((resolve4, reject) => {
1764
+ return new Promise((resolve5, reject) => {
1722
1765
  const proc = child_process.spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
1723
1766
  let stdout = "";
1724
1767
  let stderr = "";
@@ -1730,7 +1773,7 @@ async function execCommand(command, args) {
1730
1773
  });
1731
1774
  proc.on("close", (code) => {
1732
1775
  if (code === 0) {
1733
- resolve4(stdout.trim());
1776
+ resolve5(stdout.trim());
1734
1777
  } else {
1735
1778
  reject(new Error(`Command failed with exit code ${String(code)}: ${stderr}`));
1736
1779
  }
@@ -1932,7 +1975,7 @@ var MacOSKeychainKeyProvider = class _MacOSKeychainKeyProvider {
1932
1975
  }
1933
1976
  };
1934
1977
  async function execCommand2(command, args) {
1935
- return new Promise((resolve4, reject) => {
1978
+ return new Promise((resolve5, reject) => {
1936
1979
  const proc = child_process.spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
1937
1980
  let stdout = "";
1938
1981
  let stderr = "";
@@ -1944,7 +1987,7 @@ async function execCommand2(command, args) {
1944
1987
  });
1945
1988
  proc.on("close", (code) => {
1946
1989
  if (code === 0) {
1947
- resolve4(stdout.trim());
1990
+ resolve5(stdout.trim());
1948
1991
  } else {
1949
1992
  reject(new Error(`Command failed with exit code ${String(code)}: ${stderr}`));
1950
1993
  }
@@ -2105,16 +2148,19 @@ function loadLocalConfigSync(configPath) {
2105
2148
  throw error;
2106
2149
  }
2107
2150
  }
2151
+ var IDENTITY_SCHEMA_HEADER = "# yaml-language-server: $schema=https://raw.githubusercontent.com/mike-north/attest-it/main/schemas/v1/identity.schema.json\n";
2108
2152
  async function saveLocalConfig(config, configPath) {
2109
2153
  const resolvedPath = configPath ?? getLocalConfigPath();
2110
- const content = yaml.stringify(config);
2154
+ const yamlContent = yaml.stringify(config);
2155
+ const content = IDENTITY_SCHEMA_HEADER + yamlContent;
2111
2156
  const dir = path2.dirname(resolvedPath);
2112
2157
  await fs8.mkdir(dir, { recursive: true });
2113
2158
  await fs8.writeFile(resolvedPath, content, "utf8");
2114
2159
  }
2115
2160
  function saveLocalConfigSync(config, configPath) {
2116
2161
  const resolvedPath = configPath ?? getLocalConfigPath();
2117
- const content = yaml.stringify(config);
2162
+ const yamlContent = yaml.stringify(config);
2163
+ const content = IDENTITY_SCHEMA_HEADER + yamlContent;
2118
2164
  const dir = path2.dirname(resolvedPath);
2119
2165
  fs.mkdirSync(dir, { recursive: true });
2120
2166
  fs.writeFileSync(resolvedPath, content, "utf8");
@@ -2145,13 +2191,6 @@ async function savePublicKey(slug, publicKey, projectRoot = process.cwd()) {
2145
2191
  const homePath = path2.join(homeDir, `${slug}.pem`);
2146
2192
  await fs8.writeFile(homePath, publicKey, "utf8");
2147
2193
  result.homePath = homePath;
2148
- if (hasProjectConfig(projectRoot)) {
2149
- const projectDir = getProjectPublicKeysDir(projectRoot);
2150
- await fs8.mkdir(projectDir, { recursive: true });
2151
- const projectPath = path2.join(projectDir, `${slug}.pem`);
2152
- await fs8.writeFile(projectPath, publicKey, "utf8");
2153
- result.projectPath = projectPath;
2154
- }
2155
2194
  return result;
2156
2195
  }
2157
2196
  function savePublicKeySync(slug, publicKey, projectRoot = process.cwd()) {
@@ -2163,13 +2202,6 @@ function savePublicKeySync(slug, publicKey, projectRoot = process.cwd()) {
2163
2202
  const homePath = path2.join(homeDir, `${slug}.pem`);
2164
2203
  fs.writeFileSync(homePath, publicKey, "utf8");
2165
2204
  result.homePath = homePath;
2166
- if (hasProjectConfig(projectRoot)) {
2167
- const projectDir = getProjectPublicKeysDir(projectRoot);
2168
- fs.mkdirSync(projectDir, { recursive: true });
2169
- const projectPath = path2.join(projectDir, `${slug}.pem`);
2170
- fs.writeFileSync(projectPath, publicKey, "utf8");
2171
- result.projectPath = projectPath;
2172
- }
2173
2205
  return result;
2174
2206
  }
2175
2207
 
@@ -2609,7 +2641,7 @@ var YubiKeyProvider = class _YubiKeyProvider {
2609
2641
  }
2610
2642
  };
2611
2643
  async function execCommand3(command, args) {
2612
- return new Promise((resolve4, reject) => {
2644
+ return new Promise((resolve5, reject) => {
2613
2645
  const proc = child_process.spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
2614
2646
  let stdout = "";
2615
2647
  let stderr = "";
@@ -2621,7 +2653,7 @@ async function execCommand3(command, args) {
2621
2653
  });
2622
2654
  proc.on("close", (code) => {
2623
2655
  if (code === 0) {
2624
- resolve4(stdout.trim());
2656
+ resolve5(stdout.trim());
2625
2657
  } else {
2626
2658
  reject(new Error(`Command failed with exit code ${String(code)}: ${stderr}`));
2627
2659
  }
@@ -2924,8 +2956,8 @@ ${issues}`);
2924
2956
  }
2925
2957
  return result.data;
2926
2958
  }
2927
- async function readSeals(dir) {
2928
- const sealsPath = path2__namespace.join(dir, ".attest-it", "seals.json");
2959
+ async function readSeals(dir, sealsPathOverride) {
2960
+ const sealsPath = sealsPathOverride ? path2__namespace.resolve(dir, sealsPathOverride) : path2__namespace.join(dir, ".attest-it", "seals.json");
2929
2961
  try {
2930
2962
  const content = await fs__namespace.promises.readFile(sealsPath, "utf8");
2931
2963
  return parseSealsContent(content);
@@ -2941,8 +2973,8 @@ async function readSeals(dir) {
2941
2973
  );
2942
2974
  }
2943
2975
  }
2944
- function readSealsSync(dir) {
2945
- const sealsPath = path2__namespace.join(dir, ".attest-it", "seals.json");
2976
+ function readSealsSync(dir, sealsPathOverride) {
2977
+ const sealsPath = sealsPathOverride ? path2__namespace.resolve(dir, sealsPathOverride) : path2__namespace.join(dir, ".attest-it", "seals.json");
2946
2978
  try {
2947
2979
  const content = fs__namespace.readFileSync(sealsPath, "utf8");
2948
2980
  return parseSealsContent(content);
@@ -2958,11 +2990,11 @@ function readSealsSync(dir) {
2958
2990
  );
2959
2991
  }
2960
2992
  }
2961
- async function writeSeals(dir, sealsFile) {
2962
- const attestItDir = path2__namespace.join(dir, ".attest-it");
2963
- const sealsPath = path2__namespace.join(attestItDir, "seals.json");
2993
+ async function writeSeals(dir, sealsFile, sealsPathOverride) {
2994
+ const sealsPath = sealsPathOverride ? path2__namespace.resolve(dir, sealsPathOverride) : path2__namespace.join(dir, ".attest-it", "seals.json");
2995
+ const sealsDir = path2__namespace.dirname(sealsPath);
2964
2996
  try {
2965
- await fs__namespace.promises.mkdir(attestItDir, { recursive: true });
2997
+ await fs__namespace.promises.mkdir(sealsDir, { recursive: true });
2966
2998
  const content = JSON.stringify(sealsFile, null, 2) + "\n";
2967
2999
  await fs__namespace.promises.writeFile(sealsPath, content, "utf8");
2968
3000
  } catch (error) {
@@ -2971,11 +3003,11 @@ async function writeSeals(dir, sealsFile) {
2971
3003
  );
2972
3004
  }
2973
3005
  }
2974
- function writeSealsSync(dir, sealsFile) {
2975
- const attestItDir = path2__namespace.join(dir, ".attest-it");
2976
- const sealsPath = path2__namespace.join(attestItDir, "seals.json");
3006
+ function writeSealsSync(dir, sealsFile, sealsPathOverride) {
3007
+ const sealsPath = sealsPathOverride ? path2__namespace.resolve(dir, sealsPathOverride) : path2__namespace.join(dir, ".attest-it", "seals.json");
3008
+ const sealsDir = path2__namespace.dirname(sealsPath);
2977
3009
  try {
2978
- fs__namespace.mkdirSync(attestItDir, { recursive: true });
3010
+ fs__namespace.mkdirSync(sealsDir, { recursive: true });
2979
3011
  const content = JSON.stringify(sealsFile, null, 2) + "\n";
2980
3012
  fs__namespace.writeFileSync(sealsPath, content, "utf8");
2981
3013
  } catch (error) {