@sebastienrousseau/crypto-sdk 0.0.8 → 0.0.24
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/README.md +114 -58
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/index.d.ts +69 -241
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -139
- package/dist/key-types.d.ts +203 -0
- package/dist/key-types.d.ts.map +1 -0
- package/dist/key-types.js +3 -0
- package/dist/negotiation.d.ts +3 -0
- package/dist/negotiation.d.ts.map +1 -0
- package/dist/negotiation.js +105 -0
- package/dist/request.d.ts +15 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +111 -0
- package/dist/types.d.ts +183 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/package.json +17 -13
package/README.md
CHANGED
|
@@ -80,34 +80,71 @@ import { CryptoClient } from "@sebastienrousseau/crypto-sdk";
|
|
|
80
80
|
|
|
81
81
|
const client = new CryptoClient({
|
|
82
82
|
baseUrl: "http://localhost:3000",
|
|
83
|
+
apiKey: "<the server's CRYPTO_API_KEY>",
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
const { data } = await client.hash({ algorithm: "sha256", data: "hello" });
|
|
86
87
|
console.log(data.digest);
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
`CryptoClient` accepts two
|
|
90
|
+
`CryptoClient` accepts two authentication mechanisms. The server refuses
|
|
91
|
+
unauthenticated requests unless it runs with `ALLOW_ANONYMOUS=1`, and each
|
|
92
|
+
route needs a scope (`crypto:sign`, `crypto:keys`, ...); an API key holds
|
|
93
|
+
`crypto:admin`, which covers every scope except `crypto:keys:export`.
|
|
90
94
|
|
|
91
95
|
| Option | Header sent | Description |
|
|
92
96
|
| :------- | :------------------------------ | :--------------- |
|
|
93
97
|
| `apiKey` | `x-api-key: <value>` | Static API key |
|
|
94
98
|
| `token` | `Authorization: Bearer <value>` | JWT bearer token |
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
### Server-held keys
|
|
101
|
+
|
|
102
|
+
Private keys never cross the API. Key-generation methods return a `keyId`
|
|
103
|
+
with the public key, and the server keeps the private key; methods that
|
|
104
|
+
need it take the `keyId`. A key is usable only by the principal that
|
|
105
|
+
generated it.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const { data: key } = await client.generateKeyPair({ algorithm: "ed25519" });
|
|
109
|
+
const { data: sig } = await client.sign({ keyId: key.keyId, message: "hi" });
|
|
110
|
+
const { data: check } = await client.verify({
|
|
111
|
+
publicKey: key.publicKey,
|
|
112
|
+
message: "hi",
|
|
113
|
+
signature: sig.signature,
|
|
114
|
+
});
|
|
115
|
+
console.log(check.valid); // true
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`exportKey({ keyId })` returns the private parts, and only to a principal
|
|
119
|
+
whose token grants `crypto:keys:export` explicitly.
|
|
120
|
+
|
|
121
|
+
### Errors
|
|
122
|
+
|
|
123
|
+
Failed requests throw a `CryptoApiError` whose `body` is the server's
|
|
124
|
+
[RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem
|
|
125
|
+
(`application/problem+json`):
|
|
97
126
|
|
|
98
127
|
```ts
|
|
99
128
|
import { CryptoClient, CryptoApiError } from "@sebastienrousseau/crypto-sdk";
|
|
100
129
|
|
|
101
130
|
try {
|
|
102
|
-
await client.
|
|
131
|
+
await client.sign({ keyId: "k_AAAAAAAAAAAAAAAAAAAAAA", message: "x" });
|
|
103
132
|
} catch (err) {
|
|
104
133
|
if (err instanceof CryptoApiError) {
|
|
105
|
-
console.error(err.status); //
|
|
106
|
-
console.error(err.body.
|
|
134
|
+
console.error(err.status); // 404
|
|
135
|
+
console.error(err.body.type); // "urn:crypto-service:problem:key-not-found"
|
|
136
|
+
console.error(err.body.detail); // "Key not found"
|
|
137
|
+
console.error(err.body.code); // "KEY_NOT_FOUND"
|
|
107
138
|
}
|
|
108
139
|
}
|
|
109
140
|
```
|
|
110
141
|
|
|
142
|
+
`body` has `type`, `title`, `status`, `detail` and `instance`, plus
|
|
143
|
+
`code` for crypto-lib and key-store errors and `errors` (`{ field, message }`
|
|
144
|
+
per failed check) for validation failures. A response without a problem
|
|
145
|
+
body (for example from a proxy) becomes an `about:blank` problem for its
|
|
146
|
+
status code.
|
|
147
|
+
|
|
111
148
|
<p align="right"><a href="#contents">Back to Top</a></p>
|
|
112
149
|
|
|
113
150
|
---
|
|
@@ -150,41 +187,54 @@ signature operations.
|
|
|
150
187
|
|
|
151
188
|
## API Reference
|
|
152
189
|
|
|
153
|
-
|
|
154
|
-
`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
190
|
+
Methods return `Promise<ApiResponse<T>>` where `ApiResponse<T>` is
|
|
191
|
+
`{ data: T }`, except `getCbom()` (the CycloneDX document itself) and
|
|
192
|
+
`health()`. Methods marked _keyId_ use a server-held key; key-generation
|
|
193
|
+
methods return its `keyId` and public key, never the private key.
|
|
194
|
+
|
|
195
|
+
| Method | Endpoint | Description |
|
|
196
|
+
| :----------------------- | :------------------------------- | :--------------------------------------------- |
|
|
197
|
+
| `hash(body)` | `POST /v2/hash` | Compute a cryptographic hash |
|
|
198
|
+
| `encrypt(body)` | `POST /v2/encrypt` | Encrypt with XChaCha20-Poly1305 |
|
|
199
|
+
| `decrypt(body)` | `POST /v2/decrypt` | Decrypt ciphertext |
|
|
200
|
+
| `generateKeyPair(body?)` | `POST /v2/keys/generate` | Generate a server-held key pair |
|
|
201
|
+
| `exportKey(body)` | `POST /v2/keys/export` | Export a key's private parts (_keyId_) |
|
|
202
|
+
| `sign(body)` | `POST /v2/sign` | Sign with Ed25519 (_keyId_) |
|
|
203
|
+
| `verify(body)` | `POST /v2/verify` | Verify an Ed25519 signature |
|
|
204
|
+
| `kdf(body)` | `POST /v2/kdf` | Derive a key |
|
|
205
|
+
| `mac(body)` | `POST /v2/hmac` | Compute an HMAC |
|
|
206
|
+
| `macVerify(body)` | `POST /v2/hmac/verify` | Verify an HMAC |
|
|
207
|
+
| `passwordHash(body)` | `POST /v2/password/hash` | Hash a password with Argon2id |
|
|
208
|
+
| `passwordVerify(body)` | `POST /v2/password/verify` | Verify a password hash |
|
|
209
|
+
| `passwordEncrypt(body)` | `POST /v2/password/encrypt` | Encrypt data with a password |
|
|
210
|
+
| `passwordDecrypt(body)` | `POST /v2/password/decrypt` | Decrypt password-encrypted data |
|
|
211
|
+
| `keyWrap(body)` | `POST /v2/keys/wrap` | Wrap a key |
|
|
212
|
+
| `keyUnwrap(body)` | `POST /v2/keys/unwrap` | Unwrap a wrapped key |
|
|
213
|
+
| `secretboxSeal(body)` | `POST /v2/secretbox/seal` | Seal plaintext with a symmetric key |
|
|
214
|
+
| `secretboxOpen(body)` | `POST /v2/secretbox/open` | Open a sealed secretbox |
|
|
215
|
+
| `sealedboxSeal(body)` | `POST /v2/sealedbox/seal` | Seal plaintext for an X25519 public key |
|
|
216
|
+
| `sealedboxOpen(body)` | `POST /v2/sealedbox/open` | Open a sealed box (_keyId_) |
|
|
217
|
+
| `sealedboxSealPq(body)` | `POST /v2/sealedbox/seal-pq` | Seal plaintext for a hybrid public key pair |
|
|
218
|
+
| `sealedboxOpenPq(body)` | `POST /v2/sealedbox/open-pq` | Open a hybrid sealed box (_keyId_) |
|
|
219
|
+
| `mlKemGenerateKeyPair()` | `POST /v2/pq/keygen` | Generate a server-held ML-KEM-768 key pair |
|
|
220
|
+
| `mlKemEncapsulate(body)` | `POST /v2/pq/encapsulate` | Encapsulate to an ML-KEM-768 public key |
|
|
221
|
+
| `mlKemDecapsulate(body)` | `POST /v2/pq/decapsulate` | Decapsulate (_keyId_) |
|
|
222
|
+
| `pqGenerateKeyPair()` | `POST /v2/pq/hybrid/keygen` | Generate a server-held hybrid key pair |
|
|
223
|
+
| `pqEncapsulate(body)` | `POST /v2/pq/hybrid/encapsulate` | Encapsulate a hybrid shared secret |
|
|
224
|
+
| `pqDecapsulate(body)` | `POST /v2/pq/hybrid/decapsulate` | Decapsulate a hybrid shared secret (_keyId_) |
|
|
225
|
+
| `pqSignKeygen(body)` | `POST /v2/pq/dsa/keygen` | Generate a server-held ML-DSA key pair |
|
|
226
|
+
| `pqSign(body)` | `POST /v2/pq/dsa/sign` | Sign with ML-DSA (_keyId_; level from the key) |
|
|
227
|
+
| `pqVerify(body)` | `POST /v2/pq/dsa/verify` | Verify an ML-DSA signature |
|
|
228
|
+
| `pqHashSignKeygen(body)` | `POST /v2/pq/slh-dsa/keygen` | Generate a server-held SLH-DSA key pair |
|
|
229
|
+
| `pqHashSign(body)` | `POST /v2/pq/slh-dsa/sign` | Sign with SLH-DSA (_keyId_; variant from key) |
|
|
230
|
+
| `pqHashVerify(body)` | `POST /v2/pq/slh-dsa/verify` | Verify an SLH-DSA signature |
|
|
231
|
+
| `getDoraCompliance()` | `GET /v2/compliance/dora` | DORA self-assessment |
|
|
232
|
+
| `getCbom()` | `GET /v2/compliance/cbom` | CycloneDX 1.6 CBOM |
|
|
233
|
+
| `algorithms()` | `GET /v2/algorithms` | List all supported algorithms |
|
|
234
|
+
| `health()` | `GET /health` | Server health check |
|
|
235
|
+
|
|
236
|
+
The stream (`/v2/stream/*`), multi-recipient (`/v2/multi-recipient/encrypt`)
|
|
237
|
+
and v1 OpenPGP routes have no SDK methods yet.
|
|
188
238
|
|
|
189
239
|
<p align="right"><a href="#contents">Back to Top</a></p>
|
|
190
240
|
|
|
@@ -192,28 +242,34 @@ Every method returns `Promise<ApiResponse<T>>` where
|
|
|
192
242
|
|
|
193
243
|
All examples are self-contained TypeScript files in the `examples/`
|
|
194
244
|
directory. Each requires the crypto-server running on
|
|
195
|
-
`http://localhost:3000` (override via `CRYPTO_SERVER_URL`)
|
|
245
|
+
`http://localhost:3000` (override via `CRYPTO_SERVER_URL`) and sends
|
|
246
|
+
`CRYPTO_API_KEY` (or the JWT in `CRYPTO_TOKEN`) when set.
|
|
247
|
+
`pnpm run examples:check` type-checks them against the SDK.
|
|
196
248
|
|
|
197
249
|
```bash
|
|
198
|
-
npx ts-node examples/<name>.ts
|
|
250
|
+
CRYPTO_API_KEY=<key> npx ts-node examples/<name>.ts
|
|
199
251
|
```
|
|
200
252
|
|
|
201
|
-
| Category | Example | Purpose
|
|
202
|
-
| :----------- | :-------------------------------------- |
|
|
203
|
-
| Algorithms | [algorithms.ts](examples/algorithms.ts) | List all supported algorithms
|
|
204
|
-
| Encryption | [encrypt.ts](examples/encrypt.ts) |
|
|
205
|
-
| Hashing | [hash.ts](examples/hash.ts) | Compute SHA-256 and BLAKE2b digests
|
|
206
|
-
| KDF | [kdf.ts](examples/kdf.ts) | Key derivation with HKDF-SHA256
|
|
207
|
-
| Key Gen | [keygen.ts](examples/keygen.ts) | Generate key pairs
|
|
208
|
-
| Key Wrap | [keywrap.ts](examples/keywrap.ts) | AES key wrapping and unwrapping
|
|
209
|
-
| MAC | [mac.ts](examples/mac.ts) | HMAC-SHA256 compute and verify
|
|
210
|
-
| Passwords | [password.ts](examples/password.ts) | Argon2 hashing and password encryption
|
|
211
|
-
| PQ KEM | [pqkem.ts](examples/pqkem.ts) | Hybrid X25519 + ML-KEM key exchange
|
|
212
|
-
| PQ Sign | [pqsign.ts](examples/pqsign.ts) | ML-DSA post-quantum signing
|
|
213
|
-
| PQ Hash Sign | [pqhashsign.ts](examples/pqhashsign.ts) | SLH-DSA post-quantum signing
|
|
214
|
-
| Sealed Box | [sealedbox.ts](examples/sealedbox.ts) | Anonymous public-key encryption
|
|
215
|
-
| Secretbox | [secretbox.ts](examples/secretbox.ts) | Symmetric authenticated encryption
|
|
216
|
-
| Signing | [sign.ts](examples/sign.ts) | Ed25519 signing and verification
|
|
253
|
+
| Category | Example | Purpose |
|
|
254
|
+
| :----------- | :-------------------------------------- | :--------------------------------------------- |
|
|
255
|
+
| Algorithms | [algorithms.ts](examples/algorithms.ts) | List all supported algorithms |
|
|
256
|
+
| Encryption | [encrypt.ts](examples/encrypt.ts) | XChaCha20-Poly1305 encrypt and decrypt |
|
|
257
|
+
| Hashing | [hash.ts](examples/hash.ts) | Compute SHA-256, SHA-512 and BLAKE2b digests |
|
|
258
|
+
| KDF | [kdf.ts](examples/kdf.ts) | Key derivation with HKDF-SHA256 |
|
|
259
|
+
| Key Gen | [keygen.ts](examples/keygen.ts) | Generate server-held key pairs |
|
|
260
|
+
| Key Wrap | [keywrap.ts](examples/keywrap.ts) | AES key wrapping and unwrapping |
|
|
261
|
+
| MAC | [mac.ts](examples/mac.ts) | HMAC-SHA256 compute and verify |
|
|
262
|
+
| Passwords | [password.ts](examples/password.ts) | Argon2 hashing and password encryption |
|
|
263
|
+
| PQ KEM | [pqkem.ts](examples/pqkem.ts) | Hybrid X25519 + ML-KEM key exchange by keyId |
|
|
264
|
+
| PQ Sign | [pqsign.ts](examples/pqsign.ts) | ML-DSA post-quantum signing by keyId |
|
|
265
|
+
| PQ Hash Sign | [pqhashsign.ts](examples/pqhashsign.ts) | SLH-DSA post-quantum signing by keyId |
|
|
266
|
+
| Sealed Box | [sealedbox.ts](examples/sealedbox.ts) | Anonymous public-key encryption, open by keyId |
|
|
267
|
+
| Secretbox | [secretbox.ts](examples/secretbox.ts) | Symmetric authenticated encryption |
|
|
268
|
+
| Signing | [sign.ts](examples/sign.ts) | Ed25519 signing by keyId and verification |
|
|
269
|
+
|
|
270
|
+
`__tests__/contract.test.ts` runs the SDK against the real crypto-server
|
|
271
|
+
in process (its `fetch` forwards to Fastify's `inject`), so a drift
|
|
272
|
+
between a method and its route fails the test suite.
|
|
217
273
|
|
|
218
274
|
<p align="right"><a href="#contents">Back to Top</a></p>
|
|
219
275
|
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ApiError } from "./types";
|
|
2
|
+
export declare class CryptoApiError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly body: ApiError;
|
|
5
|
+
constructor(status: number, body: ApiError);
|
|
6
|
+
}
|
|
7
|
+
export declare function readProblem(res: Response): Promise<ApiError>;
|
|
8
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAiBxC,qBAAa,cAAe,SAAQ,KAAK;IAEvC,SAAgB,MAAM,EAAE,MAAM,CAAC;IAE/B,SAAgB,IAAI,EAAE,QAAQ,CAAC;gBAEnB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ;CAM3C;AAiBD,wBAAsB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CASlE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CryptoApiError = void 0;
|
|
4
|
+
exports.readProblem = readProblem;
|
|
5
|
+
class CryptoApiError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
body;
|
|
8
|
+
constructor(status, body) {
|
|
9
|
+
super(`API Error ${status}: ${body.detail}`);
|
|
10
|
+
this.name = "CryptoApiError";
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.body = body;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.CryptoApiError = CryptoApiError;
|
|
16
|
+
function isProblem(body) {
|
|
17
|
+
return (typeof body === "object" &&
|
|
18
|
+
body !== null &&
|
|
19
|
+
typeof body.detail === "string");
|
|
20
|
+
}
|
|
21
|
+
async function readProblem(res) {
|
|
22
|
+
const body = await res.json().catch(() => undefined);
|
|
23
|
+
if (isProblem(body))
|
|
24
|
+
return body;
|
|
25
|
+
return {
|
|
26
|
+
type: "about:blank",
|
|
27
|
+
title: res.statusText || "HTTP error",
|
|
28
|
+
status: res.status,
|
|
29
|
+
detail: `The server answered ${res.status} without a problem body`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=errors.js.map
|