@msdis/shield 0.2.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/LICENSE +140 -0
- package/README.md +106 -0
- package/dist/aead/index.d.ts +37 -0
- package/dist/aead/index.js +7 -0
- package/dist/aead/index.js.map +1 -0
- package/dist/asymmetric/index.d.ts +32 -0
- package/dist/asymmetric/index.js +6 -0
- package/dist/asymmetric/index.js.map +1 -0
- package/dist/chunk-3DQPQCAR.js +114 -0
- package/dist/chunk-3DQPQCAR.js.map +1 -0
- package/dist/chunk-3HCT6A2P.js +55 -0
- package/dist/chunk-3HCT6A2P.js.map +1 -0
- package/dist/chunk-AB2WZ7Y2.js +57 -0
- package/dist/chunk-AB2WZ7Y2.js.map +1 -0
- package/dist/chunk-BUFRR5PB.js +9 -0
- package/dist/chunk-BUFRR5PB.js.map +1 -0
- package/dist/chunk-CYIGDF63.js +30 -0
- package/dist/chunk-CYIGDF63.js.map +1 -0
- package/dist/chunk-EOXWR7DS.js +153 -0
- package/dist/chunk-EOXWR7DS.js.map +1 -0
- package/dist/chunk-FUDDBD2G.js +43 -0
- package/dist/chunk-FUDDBD2G.js.map +1 -0
- package/dist/chunk-JSKIWIEC.js +56 -0
- package/dist/chunk-JSKIWIEC.js.map +1 -0
- package/dist/chunk-JVFP2GAO.js +66 -0
- package/dist/chunk-JVFP2GAO.js.map +1 -0
- package/dist/chunk-KNCZMIZA.js +55 -0
- package/dist/chunk-KNCZMIZA.js.map +1 -0
- package/dist/chunk-MJO7IJZC.js +44 -0
- package/dist/chunk-MJO7IJZC.js.map +1 -0
- package/dist/chunk-MPWYZXW7.js +66 -0
- package/dist/chunk-MPWYZXW7.js.map +1 -0
- package/dist/chunk-OA5ARYJM.js +73 -0
- package/dist/chunk-OA5ARYJM.js.map +1 -0
- package/dist/chunk-OPHN2B3N.js +147 -0
- package/dist/chunk-OPHN2B3N.js.map +1 -0
- package/dist/chunk-RTAJJZKO.js +116 -0
- package/dist/chunk-RTAJJZKO.js.map +1 -0
- package/dist/chunk-SCHZI6YY.js +35 -0
- package/dist/chunk-SCHZI6YY.js.map +1 -0
- package/dist/chunk-T3IV7SHD.js +388 -0
- package/dist/chunk-T3IV7SHD.js.map +1 -0
- package/dist/chunk-U65A4HIY.js +133 -0
- package/dist/chunk-U65A4HIY.js.map +1 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/encoding-B-cb7Duu.d.ts +37 -0
- package/dist/errors-C79jA9vX.d.ts +65 -0
- package/dist/file-encryption/index.d.ts +135 -0
- package/dist/file-encryption/index.js +11 -0
- package/dist/file-encryption/index.js.map +1 -0
- package/dist/format-versioning/index.d.ts +37 -0
- package/dist/format-versioning/index.js +4 -0
- package/dist/format-versioning/index.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/integrity/index.d.ts +46 -0
- package/dist/integrity/index.js +6 -0
- package/dist/integrity/index.js.map +1 -0
- package/dist/kdf/index.d.ts +104 -0
- package/dist/kdf/index.js +7 -0
- package/dist/kdf/index.js.map +1 -0
- package/dist/key-management/index.d.ts +69 -0
- package/dist/key-management/index.js +10 -0
- package/dist/key-management/index.js.map +1 -0
- package/dist/migrations/index.d.ts +41 -0
- package/dist/migrations/index.js +4 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/post-quantum/index.d.ts +153 -0
- package/dist/post-quantum/index.js +3 -0
- package/dist/post-quantum/index.js.map +1 -0
- package/dist/random/index.d.ts +28 -0
- package/dist/random/index.js +5 -0
- package/dist/random/index.js.map +1 -0
- package/dist/secure-memory/index.d.ts +40 -0
- package/dist/secure-memory/index.js +5 -0
- package/dist/secure-memory/index.js.map +1 -0
- package/dist/signing/index.d.ts +41 -0
- package/dist/signing/index.js +5 -0
- package/dist/signing/index.js.map +1 -0
- package/dist/totp/index.d.ts +69 -0
- package/dist/totp/index.js +4 -0
- package/dist/totp/index.js.map +1 -0
- package/dist/vault-crypto/index.d.ts +225 -0
- package/dist/vault-crypto/index.js +465 -0
- package/dist/vault-crypto/index.js.map +1 -0
- package/dist/vault-encryption/index.d.ts +40 -0
- package/dist/vault-encryption/index.js +9 -0
- package/dist/vault-encryption/index.js.map +1 -0
- package/package.json +137 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# PolyForm Noncommercial License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree
|
|
8
|
+
to them as both strict obligations and conditions to all
|
|
9
|
+
your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the
|
|
14
|
+
software to do everything you might do with the software
|
|
15
|
+
that would otherwise infringe the licensor's copyright
|
|
16
|
+
in it for any permitted purpose. However, you may
|
|
17
|
+
only distribute the software according to [Distribution
|
|
18
|
+
License](#distribution-license) and make changes or new works
|
|
19
|
+
based on the software according to [Changes and New Works
|
|
20
|
+
License](#changes-and-new-works-license).
|
|
21
|
+
|
|
22
|
+
## Distribution License
|
|
23
|
+
|
|
24
|
+
The licensor grants you an additional copyright license
|
|
25
|
+
to distribute copies of the software. Your license
|
|
26
|
+
to distribute covers distributing the software with
|
|
27
|
+
changes and new works permitted by [Changes and New Works
|
|
28
|
+
License](#changes-and-new-works-license).
|
|
29
|
+
|
|
30
|
+
## Notices
|
|
31
|
+
|
|
32
|
+
You must ensure that anyone who gets a copy of any part of
|
|
33
|
+
the software from you also gets a copy of these terms or the
|
|
34
|
+
URL for them above, as well as copies of any plain-text lines
|
|
35
|
+
beginning with `Required Notice:` that the licensor provided
|
|
36
|
+
with the software. For example:
|
|
37
|
+
|
|
38
|
+
> Required Notice: Copyright Maunting Studios (https://github.com/einmalmaik/dis)
|
|
39
|
+
|
|
40
|
+
## Changes and New Works License
|
|
41
|
+
|
|
42
|
+
The licensor grants you an additional copyright license to
|
|
43
|
+
make changes and new works based on the software for any
|
|
44
|
+
permitted purpose.
|
|
45
|
+
|
|
46
|
+
## Patent License
|
|
47
|
+
|
|
48
|
+
The licensor grants you a patent license for the software that
|
|
49
|
+
covers patent claims the licensor can license, or becomes able
|
|
50
|
+
to license, that you would infringe by using the software.
|
|
51
|
+
|
|
52
|
+
## Noncommercial Purposes
|
|
53
|
+
|
|
54
|
+
Any noncommercial purpose is a permitted purpose.
|
|
55
|
+
|
|
56
|
+
## Personal Uses
|
|
57
|
+
|
|
58
|
+
Personal use for research, experiment, and testing for
|
|
59
|
+
the benefit of public knowledge, personal study, private
|
|
60
|
+
entertainment, hobby projects, amateur pursuits, or religious
|
|
61
|
+
observance, without any anticipated commercial application,
|
|
62
|
+
is use for a permitted purpose.
|
|
63
|
+
|
|
64
|
+
## Noncommercial Organizations
|
|
65
|
+
|
|
66
|
+
Use by any charitable organization, educational institution,
|
|
67
|
+
public research organization, public safety or health
|
|
68
|
+
organization, environmental protection organization,
|
|
69
|
+
or government institution is use for a permitted purpose
|
|
70
|
+
regardless of the source of funding or obligations resulting
|
|
71
|
+
from the funding.
|
|
72
|
+
|
|
73
|
+
## Fair Use
|
|
74
|
+
|
|
75
|
+
You may have "fair use" rights for the software under the
|
|
76
|
+
law. These terms do not limit them.
|
|
77
|
+
|
|
78
|
+
## No Other Rights
|
|
79
|
+
|
|
80
|
+
These terms do not allow you to sublicense or transfer any of
|
|
81
|
+
your licenses to anyone else, or prevent the licensor from
|
|
82
|
+
granting licenses to anyone else. These terms do not imply
|
|
83
|
+
any other licenses.
|
|
84
|
+
|
|
85
|
+
## Patent Defense
|
|
86
|
+
|
|
87
|
+
If you make any written claim that the software infringes or
|
|
88
|
+
contributes to infringement of any patent, your patent license
|
|
89
|
+
for the software granted under these terms ends immediately. If
|
|
90
|
+
your company makes such a claim, your patent license ends
|
|
91
|
+
immediately for work on behalf of your company.
|
|
92
|
+
|
|
93
|
+
## Violations
|
|
94
|
+
|
|
95
|
+
The first time you are notified in writing that you have
|
|
96
|
+
violated any of these terms, or done anything with the software
|
|
97
|
+
not covered by your licenses, your licenses can nonetheless
|
|
98
|
+
continue if you come into full compliance with these terms,
|
|
99
|
+
and take practical steps to correct past violations, within
|
|
100
|
+
32 days of receiving notice. Otherwise, all your licenses
|
|
101
|
+
end immediately.
|
|
102
|
+
|
|
103
|
+
## No Liability
|
|
104
|
+
|
|
105
|
+
As far as the law allows, the software comes as is, without
|
|
106
|
+
any warranty or condition, and the licensor will not be
|
|
107
|
+
liable to you for any damages arising out of these terms or
|
|
108
|
+
the use or nature of the software, under any kind of legal
|
|
109
|
+
claim.
|
|
110
|
+
|
|
111
|
+
## Definitions
|
|
112
|
+
|
|
113
|
+
The **licensor** is the individual or entity offering these
|
|
114
|
+
terms, and the **software** is the software the licensor makes
|
|
115
|
+
available under these terms.
|
|
116
|
+
|
|
117
|
+
**You** refers to the individual or entity agreeing to these
|
|
118
|
+
terms.
|
|
119
|
+
|
|
120
|
+
**Your company** is any legal entity, sole proprietorship,
|
|
121
|
+
or other kind of organization that you work for, plus all
|
|
122
|
+
organizations that have control over, are under the control of,
|
|
123
|
+
or are under common control with that organization. **Control**
|
|
124
|
+
means ownership of substantially all the assets of an entity,
|
|
125
|
+
or the power to direct its management and policies by vote,
|
|
126
|
+
contract, or otherwise. Control can be direct or indirect.
|
|
127
|
+
|
|
128
|
+
**Your licenses** are all the licenses granted to you for the
|
|
129
|
+
software under these terms.
|
|
130
|
+
|
|
131
|
+
**Use** means anything you do with the software requiring one
|
|
132
|
+
of your licenses.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
Required Notice: Copyright (c) 2025-2026 Maunting Studios
|
|
137
|
+
|
|
138
|
+
For commercial licensing of DIS — Defensive Integration Shield,
|
|
139
|
+
contact the licensor. See `docs/licensing.md` for the full
|
|
140
|
+
licensing rationale and dual-licensing options.
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# DIS — Defensive Integration Shield
|
|
2
|
+
|
|
3
|
+
> **Powered by DIS — Defensive Integration Shield**
|
|
4
|
+
|
|
5
|
+
DIS is a central, framework-agnostic cryptographic security platform. It is the
|
|
6
|
+
single place where encryption, key management, secure data formats, KDFs,
|
|
7
|
+
versioning, and future migrations live — so that consuming applications
|
|
8
|
+
(Singra Vault, Singra Premium, and future projects) hold **no direct crypto
|
|
9
|
+
logic** and depend only on stable, versioned public APIs.
|
|
10
|
+
|
|
11
|
+
DIS **does not invent cryptography**. It composes audited, established
|
|
12
|
+
primitives:
|
|
13
|
+
|
|
14
|
+
| Concern | Primitive | Source |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Password KDF | Argon2id (versioned params) | [`hash-wasm`](https://github.com/Daninet/hash-wasm) |
|
|
17
|
+
| Authenticated encryption | AES-256-GCM (96-bit IV, 128-bit tag) | WebCrypto `SubtleCrypto` |
|
|
18
|
+
| Key separation / wrapping | HKDF-SHA-256 + AES-GCM | WebCrypto |
|
|
19
|
+
| Random | CSPRNG | WebCrypto `getRandomValues` |
|
|
20
|
+
| Post-quantum (optional) | ML-KEM-768 hybrid | [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum) |
|
|
21
|
+
|
|
22
|
+
## Status
|
|
23
|
+
|
|
24
|
+
This is the **foundation release** (`0.1.0`). The pure, portable primitives
|
|
25
|
+
(encoding, random, secure memory, KDF, AEAD, vault-entry encryption, key
|
|
26
|
+
wrapping/rotation, chunked file encryption, integrity, format versioning,
|
|
27
|
+
migration framework) are implemented and tested. The post-quantum hybrid
|
|
28
|
+
sharing layer and the application cutover are tracked in
|
|
29
|
+
[`docs/migration-plan.md`](docs/migration-plan.md).
|
|
30
|
+
|
|
31
|
+
## Branding
|
|
32
|
+
|
|
33
|
+
DIS exports a `DIS_BRANDING` string constant for applications that want to
|
|
34
|
+
display a "Powered by DIS — Defensive Integration Shield" badge. This is
|
|
35
|
+
optional; the branding requirements live in [`docs/licensing.md`](docs/licensing.md).
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { DIS_BRANDING } from '@dis/shield';
|
|
39
|
+
// 'Powered by DIS — Defensive Integration Shield'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @dis/shield
|
|
46
|
+
# Optional, only if you use the post-quantum sharing module:
|
|
47
|
+
npm install @noble/post-quantum
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Node `>=20.19.0` or a browser with WebCrypto.
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import {
|
|
56
|
+
deriveMasterKey,
|
|
57
|
+
encryptVaultEntry,
|
|
58
|
+
decryptVaultEntry,
|
|
59
|
+
createWrappedUserKey,
|
|
60
|
+
rotateEncryptionKeys,
|
|
61
|
+
} from '@dis/shield';
|
|
62
|
+
|
|
63
|
+
const salt = '...'; // base64, stored per account
|
|
64
|
+
const kek = await deriveMasterKey(masterPassword, salt); // Argon2id -> AES-GCM key
|
|
65
|
+
|
|
66
|
+
const sealed = await encryptVaultEntry({ password: 's3cret' }, kek, entryId);
|
|
67
|
+
const data = await decryptVaultEntry(sealed, kek, entryId);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Narrow imports are tree-shakeable:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { encryptBytes } from '@dis/shield/aead';
|
|
74
|
+
import { deriveRawKey } from '@dis/shield/kdf';
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Modules
|
|
78
|
+
|
|
79
|
+
| Entry point | Responsibility |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| `@dis/shield/core` | Errors, encoding, constants, crypto-provider abstraction |
|
|
82
|
+
| `@dis/shield/random` | Secure random bytes / UUIDs |
|
|
83
|
+
| `@dis/shield/secure-memory` | `SecureBuffer` for key material |
|
|
84
|
+
| `@dis/shield/kdf` | Argon2id, versioned parameters, HKDF strengthening |
|
|
85
|
+
| `@dis/shield/aead` | AES-256-GCM encrypt/decrypt with AAD |
|
|
86
|
+
| `@dis/shield/format-versioning` | Versioned, prefix-tagged envelopes |
|
|
87
|
+
| `@dis/shield/vault-encryption` | Vault-entry sealing (entry-id AAD binding) |
|
|
88
|
+
| `@dis/shield/file-encryption` | Chunked attachment encryption + manifests |
|
|
89
|
+
| `@dis/shield/key-management` | Content-key wrap / unwrap / rotation |
|
|
90
|
+
| `@dis/shield/post-quantum` | ML-KEM-768 + RSA-4096 hybrid key wrapping (sharing / emergency access) |
|
|
91
|
+
| `@dis/shield/integrity` | SHA-256, constant-time compare, verification |
|
|
92
|
+
| `@dis/shield/migrations` | Ordered, explicit payload migrations |
|
|
93
|
+
|
|
94
|
+
## Security
|
|
95
|
+
|
|
96
|
+
See [`docs/`](docs/) for the architecture overview, threat model,
|
|
97
|
+
trust-boundary analysis, crypto review, and migration plan. Security
|
|
98
|
+
properties are only claimed where they are backed by code, tests, or
|
|
99
|
+
documentation. Anything unverified is labelled `not verified`.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
[PolyForm Noncommercial 1.0.0](LICENSE) — source-available, free for
|
|
104
|
+
noncommercial use. **Not free for commercial use.** See
|
|
105
|
+
[`docs/licensing.md`](docs/licensing.md) for the rationale, trademark/branding
|
|
106
|
+
rules, and commercial dual-licensing.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dis-aead — authenticated encryption with associated data.
|
|
3
|
+
*
|
|
4
|
+
* Primitive: AES-256-GCM via WebCrypto. Output format is
|
|
5
|
+
* `base64(IV(12) || ciphertext || authTag(16))`, byte-compatible with Singra
|
|
6
|
+
* Vault. A fresh random 96-bit IV is generated per call. Associated data (AAD)
|
|
7
|
+
* is authenticated but not stored; the same AAD must be supplied on decrypt,
|
|
8
|
+
* which lets callers bind ciphertext to a context (e.g. an entry id) and defeat
|
|
9
|
+
* ciphertext-swap attacks.
|
|
10
|
+
*/
|
|
11
|
+
/** Encrypts bytes with AES-256-GCM. Caller still owns/wipes `plaintextBytes`. */
|
|
12
|
+
declare function encryptBytes(plaintextBytes: Uint8Array, key: CryptoKey, associatedData?: string): Promise<string>;
|
|
13
|
+
/** Decrypts AES-256-GCM bytes. Returned buffer is secret — caller must wipe it. */
|
|
14
|
+
declare function decryptBytes(encryptedBase64: string, key: CryptoKey, associatedData?: string): Promise<Uint8Array>;
|
|
15
|
+
/** Imports raw key bytes as an AES-GCM key with the given usages. */
|
|
16
|
+
declare function importAesGcmRawKey(keyBytes: Uint8Array, usages: KeyUsage[]): Promise<CryptoKey>;
|
|
17
|
+
/** Generates a fresh non-extractable AES-256-GCM key. */
|
|
18
|
+
declare function generateAesGcmKey(usages?: KeyUsage[], extractable?: boolean): Promise<CryptoKey>;
|
|
19
|
+
/**
|
|
20
|
+
* AES-256-GCM encrypt with a caller-supplied nonce and optional binary AAD.
|
|
21
|
+
* Returns raw `ciphertext || authTag` bytes (no nonce prefix). `key` may be a
|
|
22
|
+
* raw 32-byte array or an already-imported `CryptoKey`.
|
|
23
|
+
*/
|
|
24
|
+
declare function aesGcmEncrypt(key: CryptoKey | Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, associatedData?: Uint8Array): Promise<Uint8Array>;
|
|
25
|
+
/**
|
|
26
|
+
* AES-256-GCM decrypt with a caller-supplied nonce and optional binary AAD.
|
|
27
|
+
* `ciphertext` is raw `ciphertext || authTag` bytes. Throws
|
|
28
|
+
* {@link DisDecryptionError} on any failure (wrong key / tamper / AAD mismatch)
|
|
29
|
+
* without distinguishing the cause.
|
|
30
|
+
*/
|
|
31
|
+
declare function aesGcmDecrypt(key: CryptoKey | Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, associatedData?: Uint8Array): Promise<Uint8Array>;
|
|
32
|
+
/** Encrypts a UTF-8 string. The intermediate plaintext bytes are wiped. */
|
|
33
|
+
declare function encryptString(plaintext: string, key: CryptoKey, associatedData?: string): Promise<string>;
|
|
34
|
+
/** Decrypts to a UTF-8 string. The intermediate plaintext bytes are wiped. */
|
|
35
|
+
declare function decryptString(encryptedBase64: string, key: CryptoKey, associatedData?: string): Promise<string>;
|
|
36
|
+
|
|
37
|
+
export { aesGcmDecrypt, aesGcmEncrypt, decryptBytes, decryptString, encryptBytes, encryptString, generateAesGcmKey, importAesGcmRawKey };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { aesGcmDecrypt, aesGcmEncrypt, decryptBytes, decryptString, encryptBytes, encryptString, generateAesGcmKey, importAesGcmRawKey } from '../chunk-U65A4HIY.js';
|
|
2
|
+
import '../chunk-BUFRR5PB.js';
|
|
3
|
+
import '../chunk-JSKIWIEC.js';
|
|
4
|
+
import '../chunk-CYIGDF63.js';
|
|
5
|
+
import '../chunk-MJO7IJZC.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dis-asymmetric — RSA-OAEP public-key operations.
|
|
3
|
+
*
|
|
4
|
+
* Primitive: RSA-OAEP (4096-bit modulus, SHA-256) via WebCrypto. Used by the
|
|
5
|
+
* Singra sharing / emergency-access profile to wrap symmetric material for a
|
|
6
|
+
* recipient's public key. DIS does not invent any asymmetric scheme — this is
|
|
7
|
+
* a thin, audited wrapper over WebCrypto so applications never touch the raw
|
|
8
|
+
* `crypto.subtle` surface.
|
|
9
|
+
*
|
|
10
|
+
* Key material is exported/imported as JWK (the format Singra persists), so
|
|
11
|
+
* existing stored keys remain byte-compatible. Wire format for ciphertext is
|
|
12
|
+
* `base64(rsa_oaep_output)`, identical to the legacy implementation.
|
|
13
|
+
*/
|
|
14
|
+
/** RSA-OAEP modulus length in bits. Part of the key-generation format contract. */
|
|
15
|
+
declare const RSA_OAEP_MODULUS_LENGTH = 4096;
|
|
16
|
+
/**
|
|
17
|
+
* Generates an extractable RSA-OAEP-4096 key pair (SHA-256, e=65537).
|
|
18
|
+
* Extractable so the private key can be exported as JWK and wrapped at rest.
|
|
19
|
+
*/
|
|
20
|
+
declare function generateRsaOaepKeyPair(): Promise<CryptoKeyPair>;
|
|
21
|
+
/** Exports an RSA key (public or private) as a JWK object. */
|
|
22
|
+
declare function exportJwk(key: CryptoKey): Promise<JsonWebKey>;
|
|
23
|
+
/** Imports an RSA-OAEP public key (JWK) for `encrypt`. Extractable. */
|
|
24
|
+
declare function importRsaOaepPublicKey(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
25
|
+
/** Imports an RSA-OAEP private key (JWK) for `decrypt`. Non-extractable. */
|
|
26
|
+
declare function importRsaOaepPrivateKey(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
27
|
+
/** Encrypts a UTF-8 string under an RSA-OAEP public key. Returns base64. */
|
|
28
|
+
declare function rsaOaepEncrypt(plaintext: string, publicKey: CryptoKey): Promise<string>;
|
|
29
|
+
/** Decrypts base64 RSA-OAEP ciphertext under an RSA-OAEP private key. */
|
|
30
|
+
declare function rsaOaepDecrypt(ciphertextBase64: string, privateKey: CryptoKey): Promise<string>;
|
|
31
|
+
|
|
32
|
+
export { RSA_OAEP_MODULUS_LENGTH, exportJwk, generateRsaOaepKeyPair, importRsaOaepPrivateKey, importRsaOaepPublicKey, rsaOaepDecrypt, rsaOaepEncrypt };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { RSA_OAEP_MODULUS_LENGTH, exportJwk, generateRsaOaepKeyPair, importRsaOaepPrivateKey, importRsaOaepPublicKey, rsaOaepDecrypt, rsaOaepEncrypt } from '../chunk-KNCZMIZA.js';
|
|
2
|
+
import '../chunk-JSKIWIEC.js';
|
|
3
|
+
import '../chunk-CYIGDF63.js';
|
|
4
|
+
import '../chunk-MJO7IJZC.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { randomBytes } from './chunk-3HCT6A2P.js';
|
|
2
|
+
import { importAesGcmKey } from './chunk-OPHN2B3N.js';
|
|
3
|
+
import { encryptBytes, decryptBytes, decryptString } from './chunk-U65A4HIY.js';
|
|
4
|
+
import { AES_KEY_LENGTH } from './chunk-BUFRR5PB.js';
|
|
5
|
+
import { base64ToBytes } from './chunk-JSKIWIEC.js';
|
|
6
|
+
import { subtle } from './chunk-CYIGDF63.js';
|
|
7
|
+
import { DisInvalidArgumentError } from './chunk-MJO7IJZC.js';
|
|
8
|
+
|
|
9
|
+
// src/key-management/index.ts
|
|
10
|
+
var DEFAULT_KEY_WRAP_SCHEME = {
|
|
11
|
+
prefix: "usk-wrap-v2:",
|
|
12
|
+
hkdfInfo: "singra-vault-wrap-v1"
|
|
13
|
+
};
|
|
14
|
+
async function deriveWrapKeyBytes(kdfOutputBytes, scheme) {
|
|
15
|
+
const baseKey = await subtle().importKey("raw", kdfOutputBytes, "HKDF", false, [
|
|
16
|
+
"deriveBits"
|
|
17
|
+
]);
|
|
18
|
+
const bits = await subtle().deriveBits(
|
|
19
|
+
{
|
|
20
|
+
name: "HKDF",
|
|
21
|
+
hash: "SHA-256",
|
|
22
|
+
// Zero salt is correct: IKM is already high-entropy Argon2id output.
|
|
23
|
+
salt: new Uint8Array(32),
|
|
24
|
+
info: new TextEncoder().encode(scheme.hkdfInfo)
|
|
25
|
+
},
|
|
26
|
+
baseKey,
|
|
27
|
+
256
|
|
28
|
+
);
|
|
29
|
+
return new Uint8Array(bits);
|
|
30
|
+
}
|
|
31
|
+
function generateContentKeyBytes() {
|
|
32
|
+
return randomBytes(AES_KEY_LENGTH);
|
|
33
|
+
}
|
|
34
|
+
async function createWrappedUserKey(kdfOutputBytes, scheme = DEFAULT_KEY_WRAP_SCHEME) {
|
|
35
|
+
const userKeyBytes = generateContentKeyBytes();
|
|
36
|
+
let wrapKeyBytes = null;
|
|
37
|
+
try {
|
|
38
|
+
wrapKeyBytes = await deriveWrapKeyBytes(kdfOutputBytes, scheme);
|
|
39
|
+
const wrapKey = await importAesGcmKey(wrapKeyBytes);
|
|
40
|
+
const encryptedUserKey = `${scheme.prefix}${await encryptBytes(userKeyBytes, wrapKey)}`;
|
|
41
|
+
const userKey = await importAesGcmKey(userKeyBytes);
|
|
42
|
+
return { encryptedUserKey, userKey };
|
|
43
|
+
} finally {
|
|
44
|
+
userKeyBytes.fill(0);
|
|
45
|
+
wrapKeyBytes?.fill(0);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function unwrapUserKeyBytes(encryptedUserKey, kdfOutputBytes, scheme = DEFAULT_KEY_WRAP_SCHEME) {
|
|
49
|
+
let wrapKeyBytes = null;
|
|
50
|
+
try {
|
|
51
|
+
wrapKeyBytes = await deriveWrapKeyBytes(kdfOutputBytes, scheme);
|
|
52
|
+
const wrapKey = await importAesGcmKey(wrapKeyBytes);
|
|
53
|
+
if (encryptedUserKey.startsWith(scheme.prefix)) {
|
|
54
|
+
return await decryptBytes(encryptedUserKey.slice(scheme.prefix.length), wrapKey);
|
|
55
|
+
}
|
|
56
|
+
const userKeyBase64 = await decryptString(encryptedUserKey, wrapKey);
|
|
57
|
+
return base64ToBytes(userKeyBase64);
|
|
58
|
+
} finally {
|
|
59
|
+
wrapKeyBytes?.fill(0);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function unwrapUserKey(encryptedUserKey, kdfOutputBytes, scheme = DEFAULT_KEY_WRAP_SCHEME) {
|
|
63
|
+
const userKeyBytes = await unwrapUserKeyBytes(encryptedUserKey, kdfOutputBytes, scheme);
|
|
64
|
+
try {
|
|
65
|
+
return await importAesGcmKey(userKeyBytes);
|
|
66
|
+
} finally {
|
|
67
|
+
userKeyBytes.fill(0);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function createDeterministicWrappedUserKey(kdfOutputBytes, scheme = DEFAULT_KEY_WRAP_SCHEME) {
|
|
71
|
+
const userKeyBytes = new Uint8Array(kdfOutputBytes);
|
|
72
|
+
let wrapKeyBytes = null;
|
|
73
|
+
try {
|
|
74
|
+
wrapKeyBytes = await deriveWrapKeyBytes(kdfOutputBytes, scheme);
|
|
75
|
+
const wrapKey = await importAesGcmKey(wrapKeyBytes);
|
|
76
|
+
const encryptedUserKey = `${scheme.prefix}${await encryptBytes(userKeyBytes, wrapKey)}`;
|
|
77
|
+
const userKey = await importAesGcmKey(userKeyBytes);
|
|
78
|
+
return { encryptedUserKey, userKey };
|
|
79
|
+
} finally {
|
|
80
|
+
userKeyBytes.fill(0);
|
|
81
|
+
wrapKeyBytes?.fill(0);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function generateAesGcmKeyJwk() {
|
|
85
|
+
const key = await subtle().generateKey({ name: "AES-GCM", length: 256 }, true, [
|
|
86
|
+
"encrypt",
|
|
87
|
+
"decrypt"
|
|
88
|
+
]);
|
|
89
|
+
const jwk = await subtle().exportKey("jwk", key);
|
|
90
|
+
return JSON.stringify(jwk);
|
|
91
|
+
}
|
|
92
|
+
async function importAesGcmKeyFromJwk(jwkString, usages) {
|
|
93
|
+
const jwk = JSON.parse(jwkString);
|
|
94
|
+
return subtle().importKey("jwk", jwk, { name: "AES-GCM", length: 256 }, false, [...usages]);
|
|
95
|
+
}
|
|
96
|
+
async function rotateWrappedKey(encryptedUserKey, oldKdfOutputBytes, newKdfOutputBytes, scheme = DEFAULT_KEY_WRAP_SCHEME) {
|
|
97
|
+
if (!encryptedUserKey) {
|
|
98
|
+
throw new DisInvalidArgumentError("encryptedUserKey is required");
|
|
99
|
+
}
|
|
100
|
+
const userKeyBytes = await unwrapUserKeyBytes(encryptedUserKey, oldKdfOutputBytes, scheme);
|
|
101
|
+
let newWrapKeyBytes = null;
|
|
102
|
+
try {
|
|
103
|
+
newWrapKeyBytes = await deriveWrapKeyBytes(newKdfOutputBytes, scheme);
|
|
104
|
+
const newWrapKey = await importAesGcmKey(newWrapKeyBytes);
|
|
105
|
+
return `${scheme.prefix}${await encryptBytes(userKeyBytes, newWrapKey)}`;
|
|
106
|
+
} finally {
|
|
107
|
+
userKeyBytes.fill(0);
|
|
108
|
+
newWrapKeyBytes?.fill(0);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { DEFAULT_KEY_WRAP_SCHEME, createDeterministicWrappedUserKey, createWrappedUserKey, generateAesGcmKeyJwk, generateContentKeyBytes, importAesGcmKeyFromJwk, rotateWrappedKey, unwrapUserKey, unwrapUserKeyBytes };
|
|
113
|
+
//# sourceMappingURL=chunk-3DQPQCAR.js.map
|
|
114
|
+
//# sourceMappingURL=chunk-3DQPQCAR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/key-management/index.ts"],"names":[],"mappings":"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","file":"chunk-3DQPQCAR.js","sourcesContent":["/**\r\n * dis-key-management — key hierarchy, wrapping, and rotation.\r\n *\r\n * Implements the two-tier key model used by Singra Vault:\r\n * - A high-entropy random *content key* (the \"UserKey\") encrypts vault data.\r\n * - The content key is wrapped under a *key-encryption key* (KEK) that is\r\n * derived from the KDF output via HKDF-Expand (domain-separated `info`).\r\n *\r\n * Because the content key is independent of the password, changing the master\r\n * password only re-wraps the content key — no vault data is re-encrypted. This\r\n * is what makes {@link rotateWrappedKey} cheap and safe.\r\n *\r\n * Wrapped keys carry a stable, versioned prefix. The default scheme is byte\r\n * compatible with Singra's `usk-wrap-v2:` envelope.\r\n */\r\n\r\nimport { decryptBytes, decryptString, encryptBytes } from '../aead/index.js';\r\nimport { importAesGcmKey } from '../kdf/index.js';\r\nimport { randomBytes } from '../random/index.js';\r\nimport { base64ToBytes } from '../core/encoding.js';\r\nimport { subtle } from '../core/provider.js';\r\nimport { AES_KEY_LENGTH } from '../core/constants.js';\r\nimport { DisInvalidArgumentError } from '../core/errors.js';\r\n\r\n/** Describes how a content key is wrapped: envelope prefix + HKDF domain. */\r\nexport interface KeyWrapScheme {\r\n /** Stable envelope prefix written for new wraps, e.g. `usk-wrap-v2:`. */\r\n readonly prefix: string;\r\n /** HKDF-Expand `info` for deriving the KEK from KDF output. */\r\n readonly hkdfInfo: string;\r\n}\r\n\r\n/**\r\n * Default wrap scheme. Byte-compatible with Singra Vault so existing\r\n * `encrypted_user_key` values decrypt unchanged. The `info` label is part of\r\n * the format contract and must not be renamed without a new scheme version.\r\n */\r\nexport const DEFAULT_KEY_WRAP_SCHEME: KeyWrapScheme = {\r\n prefix: 'usk-wrap-v2:',\r\n hkdfInfo: 'singra-vault-wrap-v1',\r\n};\r\n\r\nexport { importAesGcmKey };\r\n\r\nexport interface UserKeyBundle {\r\n /** Wrapped content key: `<prefix><base64(IV||CT||tag)>`. */\r\n readonly encryptedUserKey: string;\r\n /** Non-extractable AES-GCM content key, ready for vault operations. */\r\n readonly userKey: CryptoKey;\r\n}\r\n\r\n/** Derives the KEK from raw KDF output via HKDF-Expand (zero salt, RFC 5869). */\r\nasync function deriveWrapKeyBytes(\r\n kdfOutputBytes: Uint8Array,\r\n scheme: KeyWrapScheme,\r\n): Promise<Uint8Array> {\r\n const baseKey = await subtle().importKey('raw', kdfOutputBytes as BufferSource, 'HKDF', false, [\r\n 'deriveBits',\r\n ]);\r\n const bits = await subtle().deriveBits(\r\n {\r\n name: 'HKDF',\r\n hash: 'SHA-256',\r\n // Zero salt is correct: IKM is already high-entropy Argon2id output.\r\n salt: new Uint8Array(32) as BufferSource,\r\n info: new TextEncoder().encode(scheme.hkdfInfo) as BufferSource,\r\n },\r\n baseKey,\r\n 256,\r\n );\r\n return new Uint8Array(bits);\r\n}\r\n\r\n/** Generates fresh random content-key bytes (caller must wipe). */\r\nexport function generateContentKeyBytes(): Uint8Array {\r\n return randomBytes(AES_KEY_LENGTH);\r\n}\r\n\r\n/**\r\n * Creates a new random content key and wraps it under the KEK derived from\r\n * `kdfOutputBytes`. For new accounts. The caller still owns `kdfOutputBytes`.\r\n */\r\nexport async function createWrappedUserKey(\r\n kdfOutputBytes: Uint8Array,\r\n scheme: KeyWrapScheme = DEFAULT_KEY_WRAP_SCHEME,\r\n): Promise<UserKeyBundle> {\r\n const userKeyBytes = generateContentKeyBytes();\r\n let wrapKeyBytes: Uint8Array | null = null;\r\n try {\r\n wrapKeyBytes = await deriveWrapKeyBytes(kdfOutputBytes, scheme);\r\n const wrapKey = await importAesGcmKey(wrapKeyBytes);\r\n const encryptedUserKey = `${scheme.prefix}${await encryptBytes(userKeyBytes, wrapKey)}`;\r\n const userKey = await importAesGcmKey(userKeyBytes);\r\n return { encryptedUserKey, userKey };\r\n } finally {\r\n userKeyBytes.fill(0);\r\n wrapKeyBytes?.fill(0);\r\n }\r\n}\r\n\r\n/** Unwraps a wrapped content key to raw bytes (caller must wipe). */\r\nexport async function unwrapUserKeyBytes(\r\n encryptedUserKey: string,\r\n kdfOutputBytes: Uint8Array,\r\n scheme: KeyWrapScheme = DEFAULT_KEY_WRAP_SCHEME,\r\n): Promise<Uint8Array> {\r\n let wrapKeyBytes: Uint8Array | null = null;\r\n try {\r\n wrapKeyBytes = await deriveWrapKeyBytes(kdfOutputBytes, scheme);\r\n const wrapKey = await importAesGcmKey(wrapKeyBytes);\r\n if (encryptedUserKey.startsWith(scheme.prefix)) {\r\n return await decryptBytes(encryptedUserKey.slice(scheme.prefix.length), wrapKey);\r\n }\r\n // Legacy wrappers encrypted a base64 string (no prefix). Read-compatible.\r\n const userKeyBase64 = await decryptString(encryptedUserKey, wrapKey);\r\n return base64ToBytes(userKeyBase64);\r\n } finally {\r\n wrapKeyBytes?.fill(0);\r\n }\r\n}\r\n\r\n/** Unwraps a wrapped content key and imports it as an AES-GCM CryptoKey. */\r\nexport async function unwrapUserKey(\r\n encryptedUserKey: string,\r\n kdfOutputBytes: Uint8Array,\r\n scheme: KeyWrapScheme = DEFAULT_KEY_WRAP_SCHEME,\r\n): Promise<CryptoKey> {\r\n const userKeyBytes = await unwrapUserKeyBytes(encryptedUserKey, kdfOutputBytes, scheme);\r\n try {\r\n return await importAesGcmKey(userKeyBytes);\r\n } finally {\r\n userKeyBytes.fill(0);\r\n }\r\n}\r\n\r\n/**\r\n * Wraps a *deterministic* content key derived directly from `kdfOutputBytes`,\r\n * for EXISTING accounts migrating to the two-tier key model. The content key\r\n * equals the raw KDF output, so vault data previously encrypted directly under\r\n * that output remains readable without re-encryption. The KEK is still\r\n * domain-separated (HKDF `info`), so the wrapper is independent of the key.\r\n */\r\nexport async function createDeterministicWrappedUserKey(\r\n kdfOutputBytes: Uint8Array,\r\n scheme: KeyWrapScheme = DEFAULT_KEY_WRAP_SCHEME,\r\n): Promise<UserKeyBundle> {\r\n // Copy so callers can wipe their buffer without aliasing the returned key.\r\n const userKeyBytes = new Uint8Array(kdfOutputBytes);\r\n let wrapKeyBytes: Uint8Array | null = null;\r\n try {\r\n wrapKeyBytes = await deriveWrapKeyBytes(kdfOutputBytes, scheme);\r\n const wrapKey = await importAesGcmKey(wrapKeyBytes);\r\n const encryptedUserKey = `${scheme.prefix}${await encryptBytes(userKeyBytes, wrapKey)}`;\r\n const userKey = await importAesGcmKey(userKeyBytes);\r\n return { encryptedUserKey, userKey };\r\n } finally {\r\n userKeyBytes.fill(0);\r\n wrapKeyBytes?.fill(0);\r\n }\r\n}\r\n\r\n/** Generates a fresh AES-256-GCM key and returns it as a JWK JSON string. */\r\nexport async function generateAesGcmKeyJwk(): Promise<string> {\r\n const key = await subtle().generateKey({ name: 'AES-GCM', length: 256 }, true, [\r\n 'encrypt',\r\n 'decrypt',\r\n ]);\r\n const jwk = await subtle().exportKey('jwk', key);\r\n return JSON.stringify(jwk);\r\n}\r\n\r\n/** Imports an AES-256-GCM key from a JWK JSON string for the given usages. */\r\nexport async function importAesGcmKeyFromJwk(\r\n jwkString: string,\r\n usages: ReadonlyArray<KeyUsage>,\r\n): Promise<CryptoKey> {\r\n const jwk = JSON.parse(jwkString) as JsonWebKey;\r\n return subtle().importKey('jwk', jwk, { name: 'AES-GCM', length: 256 }, false, [...usages]);\r\n}\r\n\r\n/**\r\n * Re-wraps an existing content key under a new KDF output (new master password\r\n * / new salt). The content key itself is unchanged, so NO vault data is\r\n * re-encrypted — only the wrapper string changes.\r\n */\r\nexport async function rotateWrappedKey(\r\n encryptedUserKey: string,\r\n oldKdfOutputBytes: Uint8Array,\r\n newKdfOutputBytes: Uint8Array,\r\n scheme: KeyWrapScheme = DEFAULT_KEY_WRAP_SCHEME,\r\n): Promise<string> {\r\n if (!encryptedUserKey) {\r\n throw new DisInvalidArgumentError('encryptedUserKey is required');\r\n }\r\n const userKeyBytes = await unwrapUserKeyBytes(encryptedUserKey, oldKdfOutputBytes, scheme);\r\n let newWrapKeyBytes: Uint8Array | null = null;\r\n try {\r\n newWrapKeyBytes = await deriveWrapKeyBytes(newKdfOutputBytes, scheme);\r\n const newWrapKey = await importAesGcmKey(newWrapKeyBytes);\r\n return `${scheme.prefix}${await encryptBytes(userKeyBytes, newWrapKey)}`;\r\n } finally {\r\n userKeyBytes.fill(0);\r\n newWrapKeyBytes?.fill(0);\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getCryptoProvider } from './chunk-CYIGDF63.js';
|
|
2
|
+
import { DisInvalidArgumentError } from './chunk-MJO7IJZC.js';
|
|
3
|
+
|
|
4
|
+
// src/random/index.ts
|
|
5
|
+
function randomBytes(length) {
|
|
6
|
+
if (!Number.isInteger(length) || length <= 0) {
|
|
7
|
+
throw new DisInvalidArgumentError("randomBytes length must be a positive integer");
|
|
8
|
+
}
|
|
9
|
+
const out = new Uint8Array(length);
|
|
10
|
+
getCryptoProvider().getRandomValues(out);
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
function fillRandom(view) {
|
|
14
|
+
return getCryptoProvider().getRandomValues(view);
|
|
15
|
+
}
|
|
16
|
+
function randomInt(min, max) {
|
|
17
|
+
if (!Number.isInteger(min) || !Number.isInteger(max)) {
|
|
18
|
+
throw new DisInvalidArgumentError("randomInt bounds must be integers");
|
|
19
|
+
}
|
|
20
|
+
if (max < min) {
|
|
21
|
+
throw new DisInvalidArgumentError("randomInt max must be >= min");
|
|
22
|
+
}
|
|
23
|
+
const range = max - min + 1;
|
|
24
|
+
if (range === 1) {
|
|
25
|
+
return min;
|
|
26
|
+
}
|
|
27
|
+
const bytesNeeded = Math.ceil(Math.log2(range) / 8) || 1;
|
|
28
|
+
const maxValid = Math.floor(256 ** bytesNeeded / range) * range - 1;
|
|
29
|
+
const buffer = new Uint8Array(bytesNeeded);
|
|
30
|
+
let randomValue;
|
|
31
|
+
do {
|
|
32
|
+
getCryptoProvider().getRandomValues(buffer);
|
|
33
|
+
randomValue = 0;
|
|
34
|
+
for (let i = 0; i < bytesNeeded; i++) {
|
|
35
|
+
randomValue = randomValue << 8 | buffer[i];
|
|
36
|
+
}
|
|
37
|
+
} while (randomValue > maxValid);
|
|
38
|
+
return min + randomValue % range;
|
|
39
|
+
}
|
|
40
|
+
function randomUuid() {
|
|
41
|
+
const provider = getCryptoProvider();
|
|
42
|
+
if (typeof provider.randomUUID === "function") {
|
|
43
|
+
return provider.randomUUID();
|
|
44
|
+
}
|
|
45
|
+
const bytes = randomBytes(16);
|
|
46
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
47
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
48
|
+
const hex = [];
|
|
49
|
+
for (let i = 0; i < 16; i++) hex.push(bytes[i].toString(16).padStart(2, "0"));
|
|
50
|
+
return `${hex[0]}${hex[1]}${hex[2]}${hex[3]}-${hex[4]}${hex[5]}-${hex[6]}${hex[7]}-${hex[8]}${hex[9]}-${hex[10]}${hex[11]}${hex[12]}${hex[13]}${hex[14]}${hex[15]}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { fillRandom, randomBytes, randomInt, randomUuid };
|
|
54
|
+
//# sourceMappingURL=chunk-3HCT6A2P.js.map
|
|
55
|
+
//# sourceMappingURL=chunk-3HCT6A2P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/random/index.ts"],"names":[],"mappings":"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","file":"chunk-3HCT6A2P.js","sourcesContent":["/**\r\n * dis-random — secure random generation.\r\n *\r\n * Always sources entropy from the active crypto provider's CSPRNG\r\n * (`getRandomValues`). DIS never uses `Math.random` for any value that affects\r\n * confidentiality, integrity, or unpredictability.\r\n */\r\n\r\nimport { getCryptoProvider } from '../core/provider.js';\r\nimport { DisInvalidArgumentError } from '../core/errors.js';\r\n\r\n/** Returns `length` cryptographically secure random bytes. */\r\nexport function randomBytes(length: number): Uint8Array {\r\n if (!Number.isInteger(length) || length <= 0) {\r\n throw new DisInvalidArgumentError('randomBytes length must be a positive integer');\r\n }\r\n const out = new Uint8Array(length);\r\n getCryptoProvider().getRandomValues(out);\r\n return out;\r\n}\r\n\r\n/**\r\n * Fills an existing `ArrayBufferView` with cryptographically secure random\r\n * bytes in place. Used by callers that own a buffer they want to overwrite\r\n * (e.g. secure-memory scrubbing) without allocating a second array.\r\n */\r\nexport function fillRandom<T extends ArrayBufferView>(view: T): T {\r\n return getCryptoProvider().getRandomValues(view);\r\n}\r\n\r\n/**\r\n * Returns a cryptographically secure random integer in the inclusive range\r\n * `[min, max]` using rejection sampling to avoid modulo bias.\r\n *\r\n * Byte-for-byte equivalent to the `getSecureRandomInt` helpers in Singra Vault\r\n * (password generator, backup-code generator): it draws `ceil(log2(range)/8)`\r\n * bytes big-endian and rejects values above the largest multiple of `range`.\r\n */\r\nexport function randomInt(min: number, max: number): number {\r\n if (!Number.isInteger(min) || !Number.isInteger(max)) {\r\n throw new DisInvalidArgumentError('randomInt bounds must be integers');\r\n }\r\n if (max < min) {\r\n throw new DisInvalidArgumentError('randomInt max must be >= min');\r\n }\r\n const range = max - min + 1;\r\n if (range === 1) {\r\n return min;\r\n }\r\n const bytesNeeded = Math.ceil(Math.log2(range) / 8) || 1;\r\n const maxValid = Math.floor((256 ** bytesNeeded) / range) * range - 1;\r\n const buffer = new Uint8Array(bytesNeeded);\r\n let randomValue: number;\r\n do {\r\n getCryptoProvider().getRandomValues(buffer);\r\n randomValue = 0;\r\n for (let i = 0; i < bytesNeeded; i++) {\r\n randomValue = (randomValue << 8) | buffer[i]!;\r\n }\r\n } while (randomValue > maxValid);\r\n return min + (randomValue % range);\r\n}\r\n\r\n/** Returns a RFC 4122 v4 UUID using the provider's CSPRNG. */\r\nexport function randomUuid(): string {\r\n const provider = getCryptoProvider() as { randomUUID?: () => string };\r\n if (typeof provider.randomUUID === 'function') {\r\n return provider.randomUUID();\r\n }\r\n // Fallback: build a v4 UUID from random bytes.\r\n const bytes = randomBytes(16);\r\n bytes[6] = (bytes[6]! & 0x0f) | 0x40;\r\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\r\n const hex: string[] = [];\r\n for (let i = 0; i < 16; i++) hex.push(bytes[i]!.toString(16).padStart(2, '0'));\r\n return (\r\n `${hex[0]}${hex[1]}${hex[2]}${hex[3]}-${hex[4]}${hex[5]}-${hex[6]}${hex[7]}-` +\r\n `${hex[8]}${hex[9]}-${hex[10]}${hex[11]}${hex[12]}${hex[13]}${hex[14]}${hex[15]}`\r\n );\r\n}\r\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { subtle } from './chunk-CYIGDF63.js';
|
|
2
|
+
import { DisInvalidArgumentError } from './chunk-MJO7IJZC.js';
|
|
3
|
+
|
|
4
|
+
// src/signing/index.ts
|
|
5
|
+
var ECDSA_P256_ALGORITHM = { name: "ECDSA", namedCurve: "P-256" };
|
|
6
|
+
var ECDSA_P256_PARAMS = { name: "ECDSA", hash: "SHA-256" };
|
|
7
|
+
var ECDSA_P256_SIGNATURE_LENGTH = 64;
|
|
8
|
+
async function generateEcdsaP256KeyPair() {
|
|
9
|
+
const keyPair = await subtle().generateKey(
|
|
10
|
+
ECDSA_P256_ALGORITHM,
|
|
11
|
+
/* extractable */
|
|
12
|
+
false,
|
|
13
|
+
["sign", "verify"]
|
|
14
|
+
);
|
|
15
|
+
const spki = await subtle().exportKey("spki", keyPair.publicKey);
|
|
16
|
+
return {
|
|
17
|
+
privateKey: keyPair.privateKey,
|
|
18
|
+
publicKey: keyPair.publicKey,
|
|
19
|
+
publicKeySpki: new Uint8Array(spki)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function importEcdsaP256PublicKeySpki(spki) {
|
|
23
|
+
return subtle().importKey(
|
|
24
|
+
"spki",
|
|
25
|
+
spki,
|
|
26
|
+
ECDSA_P256_ALGORITHM,
|
|
27
|
+
false,
|
|
28
|
+
["verify"]
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
async function signEcdsaP256(privateKey, data) {
|
|
32
|
+
const signature = await subtle().sign(ECDSA_P256_PARAMS, privateKey, data);
|
|
33
|
+
const bytes = new Uint8Array(signature);
|
|
34
|
+
if (bytes.length !== ECDSA_P256_SIGNATURE_LENGTH) {
|
|
35
|
+
throw new DisInvalidArgumentError("unexpected ECDSA signature byte length");
|
|
36
|
+
}
|
|
37
|
+
return bytes;
|
|
38
|
+
}
|
|
39
|
+
async function verifyEcdsaP256(publicKey, signature, data) {
|
|
40
|
+
if (signature.length !== ECDSA_P256_SIGNATURE_LENGTH) {
|
|
41
|
+
throw new DisInvalidArgumentError("unexpected ECDSA signature byte length");
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return await subtle().verify(
|
|
45
|
+
ECDSA_P256_PARAMS,
|
|
46
|
+
publicKey,
|
|
47
|
+
signature,
|
|
48
|
+
data
|
|
49
|
+
);
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { ECDSA_P256_SIGNATURE_LENGTH, generateEcdsaP256KeyPair, importEcdsaP256PublicKeySpki, signEcdsaP256, verifyEcdsaP256 };
|
|
56
|
+
//# sourceMappingURL=chunk-AB2WZ7Y2.js.map
|
|
57
|
+
//# sourceMappingURL=chunk-AB2WZ7Y2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/signing/index.ts"],"names":[],"mappings":";;;;AAgBA,IAAM,oBAAA,GAAuB,EAAE,IAAA,EAAM,OAAA,EAAS,YAAY,OAAA,EAAQ;AAClE,IAAM,iBAAA,GAAoB,EAAE,IAAA,EAAM,OAAA,EAAS,MAAM,SAAA,EAAU;AAGpD,IAAM,2BAAA,GAA8B;AAa3C,eAAsB,wBAAA,GAAsD;AACxE,EAAA,MAAM,OAAA,GAAU,MAAM,MAAA,EAAO,CAAE,WAAA;AAAA,IAC3B,oBAAA;AAAA;AAAA,IACkB,KAAA;AAAA,IAClB,CAAC,QAAQ,QAAQ;AAAA,GACrB;AACA,EAAA,MAAM,OAAO,MAAM,MAAA,GAAS,SAAA,CAAU,MAAA,EAAQ,QAAQ,SAAS,CAAA;AAC/D,EAAA,OAAO;AAAA,IACH,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,WAAW,OAAA,CAAQ,SAAA;AAAA,IACnB,aAAA,EAAe,IAAI,UAAA,CAAW,IAAI;AAAA,GACtC;AACJ;AAGA,eAAsB,6BAA6B,IAAA,EAAsC;AACrF,EAAA,OAAO,QAAO,CAAE,SAAA;AAAA,IACZ,MAAA;AAAA,IACA,IAAA;AAAA,IACA,oBAAA;AAAA,IACA,KAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACb;AACJ;AAOA,eAAsB,aAAA,CAAc,YAAuB,IAAA,EAAuC;AAC9F,EAAA,MAAM,YAAY,MAAM,MAAA,GAAS,IAAA,CAAK,iBAAA,EAAmB,YAAY,IAAoB,CAAA;AACzF,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,SAAS,CAAA;AACtC,EAAA,IAAI,KAAA,CAAM,WAAW,2BAAA,EAA6B;AAC9C,IAAA,MAAM,IAAI,wBAAwB,wCAAwC,CAAA;AAAA,EAC9E;AACA,EAAA,OAAO,KAAA;AACX;AAOA,eAAsB,eAAA,CAClB,SAAA,EACA,SAAA,EACA,IAAA,EACgB;AAChB,EAAA,IAAI,SAAA,CAAU,WAAW,2BAAA,EAA6B;AAClD,IAAA,MAAM,IAAI,wBAAwB,wCAAwC,CAAA;AAAA,EAC9E;AACA,EAAA,IAAI;AACA,IAAA,OAAO,MAAM,QAAO,CAAE,MAAA;AAAA,MAClB,iBAAA;AAAA,MACA,SAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA,KACJ;AAAA,EACJ,CAAA,CAAA,MAAQ;AACJ,IAAA,OAAO,KAAA;AAAA,EACX;AACJ","file":"chunk-AB2WZ7Y2.js","sourcesContent":["/**\r\n * dis-signing — asymmetric digital signatures.\r\n *\r\n * Primitive: ECDSA over the NIST P-256 curve with SHA-256, via WebCrypto.\r\n * Public keys are exchanged as SPKI bytes; private keys are generated\r\n * non-extractable so they never leave the device. Signatures are the raw\r\n * `r || s` concatenation WebCrypto produces (fixed 64 bytes for P-256), which\r\n * is the exact wire form Singra Vault's op-log device signatures use.\r\n *\r\n * Canonicalisation of the signed payload is the caller's responsibility — DIS\r\n * signs and verifies opaque byte strings and never interprets their structure.\r\n */\r\n\r\nimport { subtle } from '../core/provider.js';\r\nimport { DisInvalidArgumentError } from '../core/errors.js';\r\n\r\nconst ECDSA_P256_ALGORITHM = { name: 'ECDSA', namedCurve: 'P-256' } as const;\r\nconst ECDSA_P256_PARAMS = { name: 'ECDSA', hash: 'SHA-256' } as const;\r\n\r\n/** Raw byte length of a P-256 ECDSA signature (`r || s`, 32 bytes each). */\r\nexport const ECDSA_P256_SIGNATURE_LENGTH = 64;\r\n\r\nexport interface EcdsaP256KeyPair {\r\n readonly privateKey: CryptoKey;\r\n readonly publicKey: CryptoKey;\r\n /** SPKI-encoded public key bytes, ready to be base64url-encoded for storage. */\r\n readonly publicKeySpki: Uint8Array;\r\n}\r\n\r\n/**\r\n * Generates a fresh non-extractable ECDSA P-256 key pair and exports the\r\n * public key as SPKI bytes.\r\n */\r\nexport async function generateEcdsaP256KeyPair(): Promise<EcdsaP256KeyPair> {\r\n const keyPair = await subtle().generateKey(\r\n ECDSA_P256_ALGORITHM,\r\n /* extractable */ false,\r\n ['sign', 'verify'],\r\n );\r\n const spki = await subtle().exportKey('spki', keyPair.publicKey);\r\n return {\r\n privateKey: keyPair.privateKey,\r\n publicKey: keyPair.publicKey,\r\n publicKeySpki: new Uint8Array(spki),\r\n };\r\n}\r\n\r\n/** Imports an SPKI-encoded P-256 public key for signature verification. */\r\nexport async function importEcdsaP256PublicKeySpki(spki: Uint8Array): Promise<CryptoKey> {\r\n return subtle().importKey(\r\n 'spki',\r\n spki as BufferSource,\r\n ECDSA_P256_ALGORITHM,\r\n false,\r\n ['verify'],\r\n );\r\n}\r\n\r\n/**\r\n * Signs `data` with an ECDSA P-256 private key, returning the raw 64-byte\r\n * `r || s` signature. Throws {@link DisInvalidArgumentError} if WebCrypto\r\n * returns an unexpected length (defends against curve/algorithm misuse).\r\n */\r\nexport async function signEcdsaP256(privateKey: CryptoKey, data: Uint8Array): Promise<Uint8Array> {\r\n const signature = await subtle().sign(ECDSA_P256_PARAMS, privateKey, data as BufferSource);\r\n const bytes = new Uint8Array(signature);\r\n if (bytes.length !== ECDSA_P256_SIGNATURE_LENGTH) {\r\n throw new DisInvalidArgumentError('unexpected ECDSA signature byte length');\r\n }\r\n return bytes;\r\n}\r\n\r\n/**\r\n * Verifies a raw 64-byte `r || s` ECDSA P-256 signature over `data`. Returns\r\n * `false` for an invalid signature; throws {@link DisInvalidArgumentError} if\r\n * the signature is not the expected length.\r\n */\r\nexport async function verifyEcdsaP256(\r\n publicKey: CryptoKey,\r\n signature: Uint8Array,\r\n data: Uint8Array,\r\n): Promise<boolean> {\r\n if (signature.length !== ECDSA_P256_SIGNATURE_LENGTH) {\r\n throw new DisInvalidArgumentError('unexpected ECDSA signature byte length');\r\n }\r\n try {\r\n return await subtle().verify(\r\n ECDSA_P256_PARAMS,\r\n publicKey,\r\n signature as BufferSource,\r\n data as BufferSource,\r\n );\r\n } catch {\r\n return false;\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// src/core/constants.ts
|
|
2
|
+
var AES_GCM_IV_LENGTH = 12;
|
|
3
|
+
var AES_GCM_TAG_LENGTH = 128;
|
|
4
|
+
var AES_KEY_LENGTH = 32;
|
|
5
|
+
var KDF_SALT_LENGTH = 16;
|
|
6
|
+
|
|
7
|
+
export { AES_GCM_IV_LENGTH, AES_GCM_TAG_LENGTH, AES_KEY_LENGTH, KDF_SALT_LENGTH };
|
|
8
|
+
//# sourceMappingURL=chunk-BUFRR5PB.js.map
|
|
9
|
+
//# sourceMappingURL=chunk-BUFRR5PB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/constants.ts"],"names":[],"mappings":";AASO,IAAM,iBAAA,GAAoB;AAG1B,IAAM,kBAAA,GAAqB;AAG3B,IAAM,cAAA,GAAiB;AAGvB,IAAM,eAAA,GAAkB","file":"chunk-BUFRR5PB.js","sourcesContent":["/**\r\n * Cryptographic constants shared across DIS modules.\r\n *\r\n * These values are part of the on-the-wire / on-disk format contract. Changing\r\n * any released constant is a breaking format change and MUST be expressed as a\r\n * new format version, never an in-place edit.\r\n */\r\n\r\n/** AES-GCM IV length in bytes (96 bits — the recommended GCM nonce size). */\r\nexport const AES_GCM_IV_LENGTH = 12;\r\n\r\n/** AES-GCM authentication tag length in bits. */\r\nexport const AES_GCM_TAG_LENGTH = 128;\r\n\r\n/** Symmetric key length in bytes (AES-256). */\r\nexport const AES_KEY_LENGTH = 32;\r\n\r\n/** Salt length in bytes (128 bits) for password-based key derivation. */\r\nexport const KDF_SALT_LENGTH = 16;\r\n"]}
|