saro-dat 0.0.0 → 0.0.1
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 +78 -16
- package/dist/crypto.d.ts +12 -0
- package/dist/crypto.js +66 -0
- package/dist/dat.bank.d.ts +12 -0
- package/dist/dat.bank.js +50 -0
- package/dist/dat.d.ts +1 -0
- package/dist/dat.js +3 -0
- package/dist/dat.key.d.ts +16 -0
- package/dist/dat.key.js +68 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +11 -0
- package/dist/signature.d.ts +14 -0
- package/dist/signature.js +137 -0
- package/dist/util.d.ts +8 -0
- package/dist/util.js +83 -0
- package/package.json +12 -4
- package/.editorconfig +0 -10
- package/PUBLISH.md +0 -22
- package/src/index.ts +0 -0
- package/tsconfig.json +0 -11
package/README.md
CHANGED
|
@@ -1,24 +1,86 @@
|
|
|
1
|
+
# DAT - Distributed Access Token
|
|
2
|
+
|
|
3
|
+
# NPM
|
|
4
|
+
```
|
|
5
|
+
saro-dat
|
|
6
|
+
```
|
|
7
|
+
|
|
1
8
|
# DAT
|
|
2
|
-
|
|
9
|
+
```
|
|
10
|
+
# Example
|
|
11
|
+
signature_algorithm: P256
|
|
12
|
+
crypto_algorithm: AES128GCMN
|
|
13
|
+
plain: 123
|
|
14
|
+
secure: asdf
|
|
15
|
+
|
|
16
|
+
# DAT result Example
|
|
17
|
+
1776530737.11.MTIz.8yKUvzs7mg3tDwdeA9I2gNOliewpTgm9OVbEY3Qh6io.qfnqmXKuNE3MfRr576rxNMCchxbY1iqC07-woJcbCudt2O0BAyK_86ypaSfLJjkGq9FZxpGrsgBDkk-xQhGvmA
|
|
18
|
+
```
|
|
3
19
|
|
|
4
|
-
|
|
5
|
-
- https://www.npmjs.com/package/saro-dat
|
|
20
|
+
> ```expire```.```kid```.```plain```.```secure```.```sign```
|
|
6
21
|
|
|
22
|
+
- ```expire```: number
|
|
23
|
+
- Unix-Timestamp (sec)
|
|
24
|
+
- ```kid```: stringifiable
|
|
25
|
+
- key id
|
|
26
|
+
- ```plain```: base64 url no pad
|
|
27
|
+
- Text Data
|
|
28
|
+
- ```secure```: base64 url no pad
|
|
29
|
+
- Encrypted Text Data
|
|
30
|
+
- ```sign```: base64 url no pad
|
|
31
|
+
- dat-bank\[kid\].sign(expire.kid.plain.secure)
|
|
32
|
+
|
|
33
|
+
# DAT KEY
|
|
7
34
|
```
|
|
8
|
-
|
|
35
|
+
# Example
|
|
36
|
+
1.2.P256.DErFl-U5h4fdbnAXTTs2GikkJgZwYXV25v2EdFeXIXs.AES128GCMN.5VEziIzCu2LRsK1XS6OYxA.1776541326.1776544626.1800
|
|
9
37
|
```
|
|
38
|
+
> ```version```.```kid```.```signature-algorithm```.```signature-key```.```crypto-algorithm```.```crypto-key```.```issue-begin```.```issue-end```.```token-ttl```
|
|
39
|
+
|
|
40
|
+
- ```version```: number
|
|
41
|
+
- dat-key format version
|
|
42
|
+
- ```kid```: stringifiable
|
|
43
|
+
- key id
|
|
44
|
+
- ```signature-algorithm```: text
|
|
45
|
+
- sign algorithm
|
|
46
|
+
- ```signature-key```: base64 url no pad
|
|
47
|
+
> The signature-key is categorized into three types: FULL (signing-key~verifying-key), SIGNING (signing-key), and VERIFYING (~verifying-key).
|
|
48
|
+
>
|
|
49
|
+
> Whether a key is for sign or verify can be distinguished by the presence of a leading tilde (~). Generally, if you output the sign key alone, the public key can be derived from it (using the private key). However, depending on the platform, this derivation feature may not be available; in such cases, you should output the full key and parse it for use.
|
|
50
|
+
- FULL: \<signing key base64\>~\<verifying key base64\>
|
|
51
|
+
- SIGNING: \<signing key base64\>
|
|
52
|
+
- VERIFYING: ~\<verifying key base64\>
|
|
53
|
+
- ```crypto-algorithm```: text
|
|
54
|
+
- crypto algorithm,
|
|
55
|
+
- ```crypto-key```: base64 url no pad
|
|
56
|
+
- crypto key
|
|
57
|
+
- ```issue-begin```: number
|
|
58
|
+
- issue begin time
|
|
59
|
+
- ```issue-end```: number
|
|
60
|
+
- issue end time
|
|
61
|
+
- ```token-ttl```: number
|
|
62
|
+
- token(dat) TTL
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## support signature algorithm
|
|
66
|
+
| name | algorithm |
|
|
67
|
+
|--------|------------|
|
|
68
|
+
| P256 | secp256r1 |
|
|
69
|
+
| P384 | secp384r1 |
|
|
70
|
+
| P521 | secp521r1 |
|
|
71
|
+
|
|
72
|
+
## support crypto algorithm
|
|
73
|
+
| name | algorithm |
|
|
74
|
+
|------------|-----------------------------|
|
|
75
|
+
| AES128GCMN | aes-128-gcm n(nonce + body) |
|
|
76
|
+
| AES256GCMN | aes-256-cbc n(nonce + body) |
|
|
77
|
+
|
|
10
78
|
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- Instance
|
|
18
|
-
- cookies
|
|
19
|
-
- ko
|
|
79
|
+
### See Also
|
|
80
|
+
- Libraries
|
|
81
|
+
- [Rust](https://github.com/saro-lab/dat-rust)
|
|
82
|
+
- [Java, Kotlin](https://github.com/saro-lab/dat-maven)
|
|
83
|
+
- DatKey Generate Service
|
|
84
|
+
- [DAT Bank: Binary Docker, Kubernetes](https://github.com/saro-lab/dat-bank)
|
|
20
85
|
|
|
21
86
|
|
|
22
|
-
## Project Info
|
|
23
|
-
- typescript
|
|
24
|
-
- yarn 3.x
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type DatCryptoAlgorithm = "AES128GCMN" | "AES256GCMN";
|
|
2
|
+
export declare class DatCryptoKey {
|
|
3
|
+
private readonly config;
|
|
4
|
+
readonly algorithm: DatCryptoAlgorithm;
|
|
5
|
+
readonly key: CryptoKey;
|
|
6
|
+
constructor(algorithm: DatCryptoAlgorithm, key: CryptoKey);
|
|
7
|
+
static generate(algorithm: DatCryptoAlgorithm): Promise<DatCryptoKey>;
|
|
8
|
+
static import(algorithmString: string, base64: string): Promise<DatCryptoKey>;
|
|
9
|
+
export(): Promise<string>;
|
|
10
|
+
encrypt(data: string | ArrayBuffer | Uint8Array | Buffer | null | undefined): Promise<ArrayBuffer>;
|
|
11
|
+
decrypt(data: ArrayBuffer | null | undefined): Promise<ArrayBuffer>;
|
|
12
|
+
}
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import { concatArrayBufferLike, decodeBase64, encodeBase64Url, toArrayBuffer } from "./index.ts";
|
|
3
|
+
const allAlgorithms = ["AES128GCMN", "AES256GCMN"];
|
|
4
|
+
const CRYPTO_CONFIG = {
|
|
5
|
+
AES128GCMN: { name: "AES-GCM", length: 128 },
|
|
6
|
+
AES256GCMN: { name: "AES-GCM", length: 256 },
|
|
7
|
+
};
|
|
8
|
+
export class DatCryptoKey {
|
|
9
|
+
config;
|
|
10
|
+
algorithm;
|
|
11
|
+
key;
|
|
12
|
+
constructor(algorithm, key) {
|
|
13
|
+
this.algorithm = algorithm;
|
|
14
|
+
this.key = key;
|
|
15
|
+
this.config = CRYPTO_CONFIG[algorithm];
|
|
16
|
+
}
|
|
17
|
+
static async generate(algorithm) {
|
|
18
|
+
const config = CRYPTO_CONFIG[algorithm];
|
|
19
|
+
if (!config)
|
|
20
|
+
throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
21
|
+
const key = await crypto.subtle.generateKey({ name: config.name, length: config.length }, true, ["encrypt", "decrypt"]);
|
|
22
|
+
return new DatCryptoKey(algorithm, key);
|
|
23
|
+
}
|
|
24
|
+
static async import(algorithmString, base64) {
|
|
25
|
+
if (!allAlgorithms.includes(algorithmString)) {
|
|
26
|
+
throw new Error(`Unsupported algorithm: ${algorithmString}`);
|
|
27
|
+
}
|
|
28
|
+
const algorithm = algorithmString;
|
|
29
|
+
const config = CRYPTO_CONFIG[algorithm];
|
|
30
|
+
if (!config)
|
|
31
|
+
throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
32
|
+
const bytes = new Uint8Array(decodeBase64(base64));
|
|
33
|
+
const key = await crypto.subtle.importKey("raw", bytes, { name: config.name }, true, ["encrypt", "decrypt"]);
|
|
34
|
+
return new DatCryptoKey(algorithm, key);
|
|
35
|
+
}
|
|
36
|
+
async export() {
|
|
37
|
+
return encodeBase64Url(await crypto.subtle.exportKey("raw", this.key));
|
|
38
|
+
}
|
|
39
|
+
async encrypt(data) {
|
|
40
|
+
const bytes = toArrayBuffer(data);
|
|
41
|
+
if (!bytes) {
|
|
42
|
+
return new ArrayBuffer(0);
|
|
43
|
+
}
|
|
44
|
+
if (this.config.name == "AES-GCM") {
|
|
45
|
+
const nonce = new Uint8Array(12);
|
|
46
|
+
crypto.getRandomValues(nonce);
|
|
47
|
+
const encrypt = await crypto.subtle.encrypt({ name: this.config.name, iv: nonce }, this.key, bytes);
|
|
48
|
+
return concatArrayBufferLike(nonce.buffer, encrypt);
|
|
49
|
+
}
|
|
50
|
+
throw new Error("Unsupported algorithm");
|
|
51
|
+
}
|
|
52
|
+
async decrypt(data) {
|
|
53
|
+
const bytes = toArrayBuffer(data);
|
|
54
|
+
if (!bytes) {
|
|
55
|
+
return new ArrayBuffer(0);
|
|
56
|
+
}
|
|
57
|
+
if (this.config.name == "AES-GCM") {
|
|
58
|
+
if (bytes.byteLength <= 12) {
|
|
59
|
+
throw new Error("Invalid data length");
|
|
60
|
+
}
|
|
61
|
+
const nonce = bytes.slice(0, 12);
|
|
62
|
+
return await crypto.subtle.decrypt({ name: this.config.name, iv: nonce }, this.key, bytes.slice(12));
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Unsupported algorithm");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DatKey } from "./index.ts";
|
|
2
|
+
import type { DatSignatureKeyOutOption } from "./index.ts";
|
|
3
|
+
export declare class DatBank {
|
|
4
|
+
private issueKey;
|
|
5
|
+
private verifyKeys;
|
|
6
|
+
constructor(issueKey?: DatKey | null, verifyKeys?: DatKey[]);
|
|
7
|
+
static import(format: string): Promise<DatBank>;
|
|
8
|
+
export(datSignatureKeyOutOption: DatSignatureKeyOutOption): Promise<string>;
|
|
9
|
+
findKey(kid: string): DatKey | null;
|
|
10
|
+
toDat(plain: ArrayBuffer | null | undefined, secure: ArrayBuffer | null | undefined): Promise<ArrayBuffer>;
|
|
11
|
+
toPayload(dat: string): Promise<ArrayBuffer>;
|
|
12
|
+
}
|
package/dist/dat.bank.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import { DatKey, } from "./index.ts";
|
|
3
|
+
export class DatBank {
|
|
4
|
+
issueKey;
|
|
5
|
+
verifyKeys;
|
|
6
|
+
constructor(issueKey = null, verifyKeys = []) {
|
|
7
|
+
this.issueKey = issueKey;
|
|
8
|
+
this.verifyKeys = verifyKeys;
|
|
9
|
+
}
|
|
10
|
+
static async import(format) {
|
|
11
|
+
const now = Math.floor(new Date().getTime() / 1000);
|
|
12
|
+
const lines = format.split('\n').map(e => e.trim()).filter(e => !!e);
|
|
13
|
+
let keys = [];
|
|
14
|
+
for (const line of lines) {
|
|
15
|
+
keys.push(await DatKey.import(line));
|
|
16
|
+
}
|
|
17
|
+
keys.sort((a, b) => a.issueBegin - b.issueBegin);
|
|
18
|
+
const issueKey = keys.findLast(e => e.issueBegin <= now && e.issueEnd > now) || null;
|
|
19
|
+
return new DatBank(issueKey, keys);
|
|
20
|
+
}
|
|
21
|
+
async export(datSignatureKeyOutOption) {
|
|
22
|
+
let lines = [];
|
|
23
|
+
for (const key of this.verifyKeys) {
|
|
24
|
+
lines.push(await key.export(datSignatureKeyOutOption));
|
|
25
|
+
}
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
}
|
|
28
|
+
findKey(kid) {
|
|
29
|
+
return this.verifyKeys.find(e => e.kid === kid) || null;
|
|
30
|
+
}
|
|
31
|
+
async toDat(plain, secure) {
|
|
32
|
+
if (this.issueKey) {
|
|
33
|
+
return this.issueKey.toDat(plain, secure);
|
|
34
|
+
}
|
|
35
|
+
throw new Error("issue key does not exist");
|
|
36
|
+
}
|
|
37
|
+
async toPayload(dat) {
|
|
38
|
+
if (!dat) {
|
|
39
|
+
throw new Error("dat is required");
|
|
40
|
+
}
|
|
41
|
+
const kid = dat.split('.')?.[1] || null;
|
|
42
|
+
if (kid != null) {
|
|
43
|
+
const key = this.findKey(kid);
|
|
44
|
+
if (key) {
|
|
45
|
+
return key.toPayload(dat);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw new Error("invalid dat");
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/dat.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function dat(key: string): string;
|
package/dist/dat.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DatCryptoKey, DatSignatureKey } from "./index.ts";
|
|
2
|
+
import type { DatSignatureKeyOutOption } from "./index.ts";
|
|
3
|
+
export declare class DatKey {
|
|
4
|
+
readonly kid: string;
|
|
5
|
+
readonly signatureKey: DatSignatureKey;
|
|
6
|
+
readonly cryptoKey: DatCryptoKey;
|
|
7
|
+
readonly issueBegin: number;
|
|
8
|
+
readonly issueEnd: number;
|
|
9
|
+
readonly tokenTtl: number;
|
|
10
|
+
constructor(kid: string, signatureKey: DatSignatureKey, cryptoKey: DatCryptoKey, issueBegin: number, issueEnd: number, tokenTtl: number);
|
|
11
|
+
export(datSignatureKeyOutOption: DatSignatureKeyOutOption): Promise<string>;
|
|
12
|
+
static import(format: string): Promise<DatKey>;
|
|
13
|
+
private static _import_ver_2;
|
|
14
|
+
toDat(plain: ArrayBuffer | null | undefined, secure: ArrayBuffer | null | undefined): Promise<ArrayBuffer>;
|
|
15
|
+
toPayload(dat: string): Promise<ArrayBuffer>;
|
|
16
|
+
}
|
package/dist/dat.key.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import { DAT_VERSION, DatCryptoKey, DatSignatureKey, toArrayBuffer, } from "./index.ts";
|
|
3
|
+
export class DatKey {
|
|
4
|
+
kid;
|
|
5
|
+
signatureKey;
|
|
6
|
+
cryptoKey;
|
|
7
|
+
issueBegin;
|
|
8
|
+
issueEnd;
|
|
9
|
+
tokenTtl;
|
|
10
|
+
constructor(kid, signatureKey, cryptoKey, issueBegin, issueEnd, tokenTtl) {
|
|
11
|
+
if (kid.match(/[.\r\n]/) != null) {
|
|
12
|
+
throw new Error(`Invalid kid: ${kid}`);
|
|
13
|
+
}
|
|
14
|
+
if (isNaN(issueBegin) || isNaN(issueEnd) || isNaN(tokenTtl)) {
|
|
15
|
+
throw new Error(`Invalid issueBegin, issueEnd, tokenTtl: ${issueBegin}, ${issueEnd}, ${tokenTtl}`);
|
|
16
|
+
}
|
|
17
|
+
this.kid = kid;
|
|
18
|
+
this.signatureKey = signatureKey;
|
|
19
|
+
this.cryptoKey = cryptoKey;
|
|
20
|
+
this.issueBegin = Math.floor(issueBegin);
|
|
21
|
+
this.issueEnd = Math.floor(issueEnd);
|
|
22
|
+
this.tokenTtl = Math.floor(tokenTtl);
|
|
23
|
+
}
|
|
24
|
+
async export(datSignatureKeyOutOption) {
|
|
25
|
+
const kid = this.kid;
|
|
26
|
+
const signAlg = this.signatureKey.algorithm;
|
|
27
|
+
const signKey = await this.signatureKey.export(datSignatureKeyOutOption);
|
|
28
|
+
const cryptoAlg = this.cryptoKey.algorithm;
|
|
29
|
+
const cryptoKey = await this.cryptoKey.export();
|
|
30
|
+
const issueBegin = this.issueBegin;
|
|
31
|
+
const issueEnd = this.issueEnd;
|
|
32
|
+
const tokenTtl = this.tokenTtl;
|
|
33
|
+
return `${DAT_VERSION}.${kid}.${signAlg}.${signKey}.${cryptoAlg}.${cryptoKey}.${issueBegin}.${issueEnd}.${tokenTtl}`;
|
|
34
|
+
}
|
|
35
|
+
static async import(format) {
|
|
36
|
+
const split = format.split(".");
|
|
37
|
+
switch (split[0] || '0') {
|
|
38
|
+
case '2':
|
|
39
|
+
case '1':
|
|
40
|
+
if (split.length == 9) {
|
|
41
|
+
return await DatKey._import_ver_2(split);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw new Error("Invalid format");
|
|
45
|
+
}
|
|
46
|
+
static async _import_ver_2(split) {
|
|
47
|
+
const kid = split[1];
|
|
48
|
+
const signKey = await DatSignatureKey.import(split[2], split[3]);
|
|
49
|
+
const cryptoKey = await DatCryptoKey.import(split[4], split[5]);
|
|
50
|
+
const issueBegin = Number(split[6]);
|
|
51
|
+
const issueEnd = Number(split[7]);
|
|
52
|
+
const tokenTtl = Number(split[8]);
|
|
53
|
+
return new DatKey(kid, signKey, cryptoKey, issueBegin, issueEnd, tokenTtl);
|
|
54
|
+
}
|
|
55
|
+
async toDat(plain, secure) {
|
|
56
|
+
const bytes = toArrayBuffer(plain);
|
|
57
|
+
if (!bytes) {
|
|
58
|
+
return new ArrayBuffer(0);
|
|
59
|
+
}
|
|
60
|
+
throw new Error("Unsupported algorithm");
|
|
61
|
+
}
|
|
62
|
+
async toPayload(dat) {
|
|
63
|
+
if (!dat) {
|
|
64
|
+
throw new Error("dat is required");
|
|
65
|
+
}
|
|
66
|
+
throw new Error("Unsupported algorithm");
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DAT_VERSION = '2';
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
export * from './crypto.ts';
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
export * from './dat.key.ts';
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
export * from './dat.bank.ts';
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
export * from './signature.ts';
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
export * from './util.ts';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type DatSignatureAlgorithm = "P256" | "P384" | "P521";
|
|
2
|
+
export type DatSignatureKeyOutOption = "FULL" | "SIGNING" | "VERIFYING";
|
|
3
|
+
export declare class DatSignatureKey {
|
|
4
|
+
private readonly config;
|
|
5
|
+
readonly algorithm: DatSignatureAlgorithm;
|
|
6
|
+
readonly signingKey: CryptoKey | null;
|
|
7
|
+
readonly verifyingKey: CryptoKey;
|
|
8
|
+
constructor(algorithm: DatSignatureAlgorithm, SigningKey: CryptoKey | null, VerifyingKey: CryptoKey);
|
|
9
|
+
static generate(algorithm: DatSignatureAlgorithm): Promise<DatSignatureKey>;
|
|
10
|
+
static import(algorithmString: string, format: string): Promise<DatSignatureKey>;
|
|
11
|
+
export(option: DatSignatureKeyOutOption): Promise<string>;
|
|
12
|
+
sign(data: ArrayBuffer | null | undefined): Promise<ArrayBuffer>;
|
|
13
|
+
verify(data: ArrayBuffer | null | undefined, signature: ArrayBuffer | null | undefined): Promise<boolean>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import { decodeBase64, encodeBase64Url, normalizeBase64Url } from "./index.ts";
|
|
3
|
+
import { p256, p384, p521 } from '@noble/curves/nist.js';
|
|
4
|
+
const allAlgorithms = ["P256", "P384", "P521"];
|
|
5
|
+
const SIGNATURE_CONFIG = {
|
|
6
|
+
P256: { name: "ECDSA", curve: 'P-256', hash: 'SHA-256' },
|
|
7
|
+
P384: { name: "ECDSA", curve: 'P-384', hash: 'SHA-384' },
|
|
8
|
+
P521: { name: "ECDSA", curve: 'P-521', hash: 'SHA-512' },
|
|
9
|
+
};
|
|
10
|
+
export class DatSignatureKey {
|
|
11
|
+
config;
|
|
12
|
+
algorithm;
|
|
13
|
+
signingKey;
|
|
14
|
+
verifyingKey;
|
|
15
|
+
constructor(algorithm, SigningKey, VerifyingKey) {
|
|
16
|
+
this.algorithm = algorithm;
|
|
17
|
+
this.signingKey = SigningKey;
|
|
18
|
+
this.verifyingKey = VerifyingKey;
|
|
19
|
+
this.config = SIGNATURE_CONFIG[algorithm];
|
|
20
|
+
}
|
|
21
|
+
static async generate(algorithm) {
|
|
22
|
+
const config = SIGNATURE_CONFIG[algorithm];
|
|
23
|
+
if (!config)
|
|
24
|
+
throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
25
|
+
const { publicKey, privateKey } = await crypto.subtle.generateKey({ name: config.name, namedCurve: config.curve, }, true, ["sign", "verify"]);
|
|
26
|
+
return new DatSignatureKey(algorithm, privateKey, publicKey);
|
|
27
|
+
}
|
|
28
|
+
static async import(algorithmString, format) {
|
|
29
|
+
if (!allAlgorithms.includes(algorithmString)) {
|
|
30
|
+
throw new Error(`Unsupported algorithm: ${algorithmString}`);
|
|
31
|
+
}
|
|
32
|
+
const algorithm = algorithmString;
|
|
33
|
+
const config = SIGNATURE_CONFIG[algorithm];
|
|
34
|
+
if (!config)
|
|
35
|
+
throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
36
|
+
if (!format) {
|
|
37
|
+
throw new Error("Invalid format");
|
|
38
|
+
}
|
|
39
|
+
const split = format.split("~");
|
|
40
|
+
let signingKeyBase64 = '';
|
|
41
|
+
let signingKey = null;
|
|
42
|
+
let verifyingKeyBytes = null;
|
|
43
|
+
let verifyingKey = null;
|
|
44
|
+
let ecdsa;
|
|
45
|
+
switch (algorithm) {
|
|
46
|
+
case "P256":
|
|
47
|
+
ecdsa = p256;
|
|
48
|
+
break;
|
|
49
|
+
case "P384":
|
|
50
|
+
ecdsa = p384;
|
|
51
|
+
break;
|
|
52
|
+
case "P521":
|
|
53
|
+
ecdsa = p521;
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
57
|
+
}
|
|
58
|
+
if (split.length == 1) {
|
|
59
|
+
if (split[0]) {
|
|
60
|
+
signingKeyBase64 = split[0];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (split.length == 2) {
|
|
64
|
+
if (split[0]) {
|
|
65
|
+
signingKeyBase64 = split[0];
|
|
66
|
+
}
|
|
67
|
+
if (split[1]) {
|
|
68
|
+
verifyingKeyBytes = new Uint8Array(decodeBase64(split[1]));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (signingKeyBase64) {
|
|
72
|
+
const dBytes = decodeBase64(signingKeyBase64);
|
|
73
|
+
// d로부터 공개 키(Point) 계산
|
|
74
|
+
const publicPoint = ecdsa.getPublicKey(dBytes, false);
|
|
75
|
+
// 곡선별 좌표 길이 계산: (전체 길이 - 1) / 2 - 좌표 추출
|
|
76
|
+
const coordinateLen = (publicPoint.length - 1) / 2;
|
|
77
|
+
const xBytes = publicPoint.slice(1, 1 + coordinateLen);
|
|
78
|
+
const yBytes = publicPoint.slice(1 + coordinateLen, 1 + 2 * coordinateLen);
|
|
79
|
+
const jwk = {
|
|
80
|
+
kty: "EC",
|
|
81
|
+
crv: config.curve,
|
|
82
|
+
d: signingKeyBase64,
|
|
83
|
+
x: Buffer.from(xBytes).toString('base64'),
|
|
84
|
+
y: Buffer.from(yBytes).toString('base64'),
|
|
85
|
+
ext: true,
|
|
86
|
+
};
|
|
87
|
+
signingKey = await crypto.subtle.importKey("jwk", jwk, { name: "ECDSA", namedCurve: config.curve }, true, ["sign"]);
|
|
88
|
+
}
|
|
89
|
+
if (!verifyingKeyBytes && signingKeyBase64) {
|
|
90
|
+
verifyingKeyBytes = ecdsa.getPublicKey(decodeBase64(signingKeyBase64), false);
|
|
91
|
+
}
|
|
92
|
+
if (verifyingKeyBytes) {
|
|
93
|
+
verifyingKey = await crypto.subtle.importKey("raw", verifyingKeyBytes, { name: config.name, namedCurve: config.curve }, true, ["verify"]);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
throw new Error("Invalid format");
|
|
97
|
+
}
|
|
98
|
+
return new DatSignatureKey(algorithm, signingKey, verifyingKey);
|
|
99
|
+
}
|
|
100
|
+
async export(option) {
|
|
101
|
+
let rv = "";
|
|
102
|
+
if (option == "FULL" || option == "SIGNING") {
|
|
103
|
+
if (this.signingKey) {
|
|
104
|
+
let jwk = await crypto.subtle.exportKey("jwk", this.signingKey);
|
|
105
|
+
rv += normalizeBase64Url(jwk.d);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error("this key is verifying only");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (option == "FULL" || option == "VERIFYING") {
|
|
112
|
+
rv += '~' + encodeBase64Url(await crypto.subtle.exportKey("raw", this.verifyingKey));
|
|
113
|
+
}
|
|
114
|
+
return rv;
|
|
115
|
+
}
|
|
116
|
+
async sign(data) {
|
|
117
|
+
if (!this.signingKey) {
|
|
118
|
+
throw new Error("this key is verifying only");
|
|
119
|
+
}
|
|
120
|
+
if (!data) {
|
|
121
|
+
throw new Error("data is required");
|
|
122
|
+
}
|
|
123
|
+
return crypto.subtle.sign({
|
|
124
|
+
name: this.config.name,
|
|
125
|
+
hash: { name: this.config.hash },
|
|
126
|
+
}, this.signingKey, data);
|
|
127
|
+
}
|
|
128
|
+
async verify(data, signature) {
|
|
129
|
+
if (!data || !signature) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return crypto.subtle.verify({
|
|
133
|
+
name: this.config.name,
|
|
134
|
+
hash: { name: this.config.hash },
|
|
135
|
+
}, this.verifyingKey, signature, data);
|
|
136
|
+
}
|
|
137
|
+
}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function normalizeBase64Url(base64: string): string;
|
|
2
|
+
export declare function encodeBase64Url(data: any): string;
|
|
3
|
+
export declare function decodeBase64String(b64: string | ArrayBuffer | null | undefined): string;
|
|
4
|
+
export declare function decodeBase64(b64: string | ArrayBuffer | null | undefined): Buffer;
|
|
5
|
+
export declare function concatArrayBufferLike(arr1: ArrayBufferLike, arr2: ArrayBufferLike): ArrayBuffer;
|
|
6
|
+
export declare function toArrayBuffer(data: string | ArrayBuffer | Uint8Array | SharedArrayBuffer | Buffer | null | undefined): ArrayBuffer;
|
|
7
|
+
export declare function randomString(length: number, mold: string): string;
|
|
8
|
+
export declare function randomBase62(length: number): string;
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const asciiDecoder = new TextDecoder('ascii');
|
|
2
|
+
const textEncoder = new TextEncoder();
|
|
3
|
+
const base62arr = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
4
|
+
export function normalizeBase64Url(base64) {
|
|
5
|
+
return base64.replace(/[+\/=]/g, (ch) => {
|
|
6
|
+
switch (ch) {
|
|
7
|
+
case '+': return '-';
|
|
8
|
+
case '/': return '_';
|
|
9
|
+
}
|
|
10
|
+
return '';
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function encodeBase64Url(data) {
|
|
14
|
+
if (!data) {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
if (typeof data === 'string') {
|
|
18
|
+
return normalizeBase64Url(Buffer.from(data).toString('base64'));
|
|
19
|
+
}
|
|
20
|
+
else if (data instanceof ArrayBuffer) {
|
|
21
|
+
return normalizeBase64Url(Buffer.from(data).toString('base64'));
|
|
22
|
+
}
|
|
23
|
+
else if (data instanceof Uint8Array) {
|
|
24
|
+
return normalizeBase64Url(Buffer.from(data).toString('base64'));
|
|
25
|
+
}
|
|
26
|
+
else if (data instanceof Buffer) {
|
|
27
|
+
return normalizeBase64Url(data.toString('base64'));
|
|
28
|
+
}
|
|
29
|
+
throw new Error('Unsupported data type');
|
|
30
|
+
}
|
|
31
|
+
export function decodeBase64String(b64) {
|
|
32
|
+
return decodeBase64(b64).toString('utf-8');
|
|
33
|
+
}
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
export function decodeBase64(b64) {
|
|
36
|
+
if (!b64) {
|
|
37
|
+
return Buffer.alloc(0);
|
|
38
|
+
}
|
|
39
|
+
if (typeof b64 === 'string') {
|
|
40
|
+
return Buffer.from(b64, 'base64');
|
|
41
|
+
}
|
|
42
|
+
if (b64 instanceof ArrayBuffer) {
|
|
43
|
+
return Buffer.from(asciiDecoder.decode(b64), 'base64');
|
|
44
|
+
}
|
|
45
|
+
throw new Error('Unsupported data type');
|
|
46
|
+
}
|
|
47
|
+
export function concatArrayBufferLike(arr1, arr2) {
|
|
48
|
+
const buf = new Uint8Array(arr1.byteLength + arr2.byteLength);
|
|
49
|
+
buf.set(new Uint8Array(arr1), 0);
|
|
50
|
+
buf.set(new Uint8Array(arr2), arr1.byteLength);
|
|
51
|
+
return buf.buffer;
|
|
52
|
+
}
|
|
53
|
+
export function toArrayBuffer(data) {
|
|
54
|
+
if (!data) {
|
|
55
|
+
return new ArrayBuffer(0);
|
|
56
|
+
}
|
|
57
|
+
if (typeof data === 'string') {
|
|
58
|
+
return textEncoder.encode(data).buffer;
|
|
59
|
+
}
|
|
60
|
+
if (Buffer.isBuffer(data)) {
|
|
61
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
62
|
+
}
|
|
63
|
+
if (data instanceof Uint8Array) {
|
|
64
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
65
|
+
}
|
|
66
|
+
if (data instanceof ArrayBuffer) {
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
if (data instanceof SharedArrayBuffer) {
|
|
70
|
+
const buffer = new Uint8Array(data.byteLength);
|
|
71
|
+
buffer.set(new Uint8Array(data));
|
|
72
|
+
return buffer.buffer;
|
|
73
|
+
}
|
|
74
|
+
throw new Error('Unsupported data type');
|
|
75
|
+
}
|
|
76
|
+
export function randomString(length, mold) {
|
|
77
|
+
return [...Array(length)]
|
|
78
|
+
.map(() => mold[Math.floor(Math.random() * mold.length)])
|
|
79
|
+
.join('');
|
|
80
|
+
}
|
|
81
|
+
export function randomBase62(length) {
|
|
82
|
+
return randomString(length, base62arr);
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saro-dat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "Distributed Access Token",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dat",
|
|
@@ -18,13 +18,21 @@
|
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"author": "marker",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"types": "dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist/**"
|
|
25
|
+
],
|
|
23
26
|
"scripts": {
|
|
24
27
|
"build": "tsc",
|
|
25
|
-
"test": "
|
|
28
|
+
"--test": "node --test dist/**/*.test.js",
|
|
29
|
+
"pretest": "npm run build"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
32
|
+
"@types/node": "^25.6.0",
|
|
28
33
|
"typescript": "^6.0.3"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@noble/curves": "^2.2.0"
|
|
29
37
|
}
|
|
30
38
|
}
|
package/.editorconfig
DELETED
package/PUBLISH.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# build & publish
|
|
2
|
-
|
|
3
|
-
## build
|
|
4
|
-
``` shell
|
|
5
|
-
npm install
|
|
6
|
-
```
|
|
7
|
-
|
|
8
|
-
``` shell
|
|
9
|
-
npm run build
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## login
|
|
13
|
-
``` shell
|
|
14
|
-
npm login
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## publish
|
|
18
|
-
1. build
|
|
19
|
-
2. update next version in README.md and commit
|
|
20
|
-
``` shell
|
|
21
|
-
npm publish
|
|
22
|
-
```
|
package/src/index.ts
DELETED
|
File without changes
|