iso-signatures 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.
@@ -0,0 +1,147 @@
1
+ /* eslint-disable unicorn/numeric-separators-style */
2
+ import { webcrypto } from 'iso-base/crypto'
3
+ import { u8 } from 'iso-base/utils'
4
+ import { DIDKey, keyTypeToAlg } from 'iso-did/key'
5
+ import { createEcdsaParams } from '../utils.js'
6
+
7
+ /**
8
+ * @typedef {import('../types.js').ISigner<CryptoKeyPair>} ISigner
9
+ * @typedef {Extract<import('iso-did/types').SignatureAlgorithm, 'ES256' | 'ES384' | 'ES512'>} ECDSAAlg
10
+ */
11
+
12
+ /**
13
+ * @param {string} alg
14
+ */
15
+ function asECDSAAlg(alg) {
16
+ if (alg.startsWith('ES')) {
17
+ return /** @type {ECDSAAlg} */ (alg)
18
+ }
19
+ throw new TypeError(`Unsupported algorithm ${alg}`)
20
+ }
21
+
22
+ /**
23
+ * ECDSA signer
24
+ *
25
+ * @implements {ISigner}
26
+ */
27
+ export class ECDSASigner {
28
+ /**
29
+ * @param {ECDSAAlg} alg
30
+ * @param {Uint8Array} publicKey
31
+ * @param {CryptoKeyPair} keypair
32
+ */
33
+ constructor(alg, publicKey, keypair) {
34
+ this.params = createEcdsaParams(alg)
35
+ this.keypair = keypair
36
+ this.publicKey = publicKey
37
+ this.alg = alg
38
+ this.type = this.params.namedCurve
39
+ this.did = DIDKey.fromPublicKey(this.type, this.publicKey)
40
+ }
41
+
42
+ /**
43
+ * Generate a new signer
44
+ *
45
+ * @param {ECDSAAlg} alg
46
+ */
47
+ static async generate(alg) {
48
+ const params = createEcdsaParams(alg)
49
+ const cryptoKeyPair = await webcrypto.subtle.generateKey(params, false, [
50
+ 'sign',
51
+ 'verify',
52
+ ])
53
+
54
+ const publicKey = await webcrypto.subtle.exportKey(
55
+ 'raw',
56
+ cryptoKeyPair.publicKey
57
+ )
58
+
59
+ return new ECDSASigner(alg, u8(publicKey), cryptoKeyPair)
60
+ }
61
+
62
+ /**
63
+ * Import a signer from a JWK
64
+ *
65
+ * @param {import('../types.js').PrivateKeyJwk} jwk
66
+ */
67
+ static async importJwk(jwk) {
68
+ const alg = asECDSAAlg(keyTypeToAlg(jwk.crv))
69
+ const params = createEcdsaParams(alg)
70
+ const privateKey = await webcrypto.subtle.importKey(
71
+ 'jwk',
72
+ jwk,
73
+ params,
74
+ false,
75
+ ['sign']
76
+ )
77
+ const publicKeyJWK = await webcrypto.subtle.importKey(
78
+ 'jwk',
79
+ { ...jwk, d: undefined },
80
+ params,
81
+ true,
82
+ ['verify']
83
+ )
84
+
85
+ const publicKey = await webcrypto.subtle.exportKey('raw', publicKeyJWK)
86
+
87
+ return new ECDSASigner(alg, u8(publicKey), {
88
+ privateKey,
89
+ publicKey: publicKeyJWK,
90
+ })
91
+ }
92
+
93
+ /**
94
+ * Import a signer from a CryptoKeyPair
95
+ *
96
+ * @param {CryptoKeyPair} key
97
+ */
98
+ static async import(key) {
99
+ const publicKey = await webcrypto.subtle.exportKey('raw', key.publicKey)
100
+
101
+ /** @type { ECDSAAlg } */
102
+ let alg
103
+ switch (publicKey.byteLength) {
104
+ case 65: {
105
+ alg = 'ES256'
106
+ break
107
+ }
108
+ case 97: {
109
+ alg = 'ES384'
110
+ break
111
+ }
112
+ case 133: {
113
+ alg = 'ES512'
114
+ break
115
+ }
116
+
117
+ default: {
118
+ throw new Error('Unsupported key type')
119
+ }
120
+ }
121
+ return new ECDSASigner(alg, u8(publicKey), key)
122
+ }
123
+
124
+ /**
125
+ * Sign a message
126
+ *
127
+ * @param {Uint8Array} message
128
+ */
129
+ async sign(message) {
130
+ const buf = await webcrypto.subtle.sign(
131
+ this.params,
132
+ this.keypair.privateKey,
133
+ message
134
+ )
135
+
136
+ return u8(buf)
137
+ }
138
+
139
+ /**
140
+ * Export the signer as a CryptoKeyPair
141
+ *
142
+ * @returns {CryptoKeyPair}
143
+ */
144
+ export() {
145
+ return this.keypair
146
+ }
147
+ }
@@ -0,0 +1,80 @@
1
+ /* eslint-disable unicorn/numeric-separators-style */
2
+ import { getPublicKeyAsync, signAsync, utils } from '@noble/ed25519'
3
+ import { webcrypto } from 'iso-base/crypto'
4
+ import { base64pad } from 'iso-base/rfc4648'
5
+ import { tag, untag } from 'iso-base/varint'
6
+ import { DIDKey } from 'iso-did/key'
7
+
8
+ // @ts-ignore
9
+ if (!globalThis.crypto) globalThis.crypto = webcrypto
10
+
11
+ /**
12
+ * @typedef {import('../types.js').ISigner<string>} ISigner
13
+ */
14
+
15
+ /**
16
+ * EdDSA signer
17
+ *
18
+ * @implements {ISigner}
19
+ */
20
+ export class EdDSASigner {
21
+ /** @type {Extract<import('iso-did/types').SignatureAlgorithm, "EdDSA">} */
22
+ static alg = 'EdDSA'
23
+
24
+ /** @type {Extract<import('iso-did/types').KeyType, "Ed25519">} */
25
+ static type = 'Ed25519'
26
+
27
+ static code = 0x1300
28
+
29
+ /**
30
+ * @param {Uint8Array} publicKey
31
+ * @param {Uint8Array} privateKey
32
+ */
33
+ constructor(publicKey, privateKey) {
34
+ this.privateKey = privateKey
35
+ this.publicKey = publicKey
36
+ this.alg = EdDSASigner.alg
37
+ this.type = EdDSASigner.type
38
+ this.code = EdDSASigner.code
39
+ this.did = DIDKey.fromPublicKey(EdDSASigner.type, this.publicKey)
40
+ }
41
+
42
+ /**
43
+ * Generate a new signer
44
+ *
45
+ * @param {Uint8Array} [bytes]
46
+ */
47
+ static async generate(bytes) {
48
+ const privateKey = bytes || utils.randomPrivateKey()
49
+ const publicKey = await getPublicKeyAsync(privateKey)
50
+ return new EdDSASigner(publicKey, privateKey)
51
+ }
52
+
53
+ /**
54
+ * Import a signer from a encoded string
55
+ *
56
+ * @param {string} encoded
57
+ */
58
+ static async import(encoded) {
59
+ const privateKey = untag(EdDSASigner.code, base64pad.decode(encoded))
60
+ const publicKey = await getPublicKeyAsync(privateKey)
61
+
62
+ return new EdDSASigner(publicKey, privateKey)
63
+ }
64
+
65
+ /**
66
+ * Sign a message
67
+ *
68
+ * @param {Uint8Array} message
69
+ */
70
+ async sign(message) {
71
+ return signAsync(message, this.privateKey)
72
+ }
73
+
74
+ /**
75
+ * Export the signer as a encoded string
76
+ */
77
+ export() {
78
+ return base64pad.encode(tag(EdDSASigner.code, this.privateKey))
79
+ }
80
+ }
@@ -0,0 +1,128 @@
1
+ /* eslint-disable unicorn/numeric-separators-style */
2
+ import { webcrypto } from 'iso-base/crypto'
3
+ import { u8 } from 'iso-base/utils'
4
+ import { DIDKey } from 'iso-did/key'
5
+
6
+ /**
7
+ * @typedef {import('../types.js').ISigner<CryptoKeyPair>} ISigner
8
+ */
9
+
10
+ /**
11
+ * RSA Old fission format signer
12
+ *
13
+ * @implements {ISigner}
14
+ */
15
+ export class RSAOldSigner {
16
+ /** @type {Extract<import('iso-did/types').SignatureAlgorithm, "RS256_OLD">} */
17
+ static alg = 'RS256_OLD'
18
+
19
+ /** @type {Extract<import('iso-did/types').KeyType, "RSA_OLD">} */
20
+ static type = 'RSA_OLD'
21
+
22
+ /**
23
+ * @param {Uint8Array} publicKey
24
+ * @param {CryptoKeyPair} keypair
25
+ */
26
+ constructor(publicKey, keypair) {
27
+ this.keypair = keypair
28
+ this.publicKey = publicKey
29
+ this.alg = RSAOldSigner.alg
30
+ this.type = RSAOldSigner.type
31
+ this.did = DIDKey.fromPublicKey(RSAOldSigner.type, this.publicKey)
32
+ }
33
+
34
+ /**
35
+ * Generate a new signer
36
+ */
37
+ static async generate() {
38
+ const cryptoKeyPair = await webcrypto.subtle.generateKey(
39
+ {
40
+ name: 'RSASSA-PKCS1-v1_5',
41
+ modulusLength: 2048,
42
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
43
+ hash: { name: 'SHA-256' },
44
+ },
45
+
46
+ false,
47
+ ['sign', 'verify']
48
+ )
49
+
50
+ const publicKey = await webcrypto.subtle.exportKey(
51
+ 'spki',
52
+ cryptoKeyPair.publicKey
53
+ )
54
+
55
+ return new RSAOldSigner(u8(publicKey), cryptoKeyPair)
56
+ }
57
+
58
+ /**
59
+ * Import a signer from a JWK
60
+ *
61
+ * @param {import('../types.js').PrivateKeyRSAJwk} jwk
62
+ */
63
+ static async importJwk(jwk) {
64
+ const privateKey = await webcrypto.subtle.importKey(
65
+ 'jwk',
66
+ jwk,
67
+ {
68
+ name: 'RSASSA-PKCS1-v1_5',
69
+ hash: 'SHA-256',
70
+ },
71
+ false,
72
+ ['sign']
73
+ )
74
+ const publicKeyJWK = await webcrypto.subtle.importKey(
75
+ 'jwk',
76
+ { ...jwk, d: undefined },
77
+ {
78
+ name: 'RSASSA-PKCS1-v1_5',
79
+ hash: 'SHA-256',
80
+ },
81
+ true,
82
+ ['verify']
83
+ )
84
+
85
+ const publicKey = await webcrypto.subtle.exportKey('spki', publicKeyJWK)
86
+
87
+ return new RSAOldSigner(u8(publicKey), {
88
+ privateKey,
89
+ publicKey: publicKeyJWK,
90
+ })
91
+ }
92
+
93
+ /**
94
+ * Import a signer from a keypair
95
+ *
96
+ * @param {CryptoKeyPair} keypair
97
+ */
98
+ static async import(keypair) {
99
+ const publicKey = await webcrypto.subtle.exportKey(
100
+ 'spki',
101
+ keypair.publicKey
102
+ )
103
+
104
+ return new RSAOldSigner(u8(publicKey), keypair)
105
+ }
106
+
107
+ /**
108
+ * Sign a message
109
+ *
110
+ * @param {Uint8Array} message
111
+ */
112
+ async sign(message) {
113
+ const buf = await webcrypto.subtle.sign(
114
+ { name: 'RSASSA-PKCS1-v1_5', saltLength: 128 },
115
+ this.keypair.privateKey,
116
+ message
117
+ )
118
+
119
+ return u8(buf)
120
+ }
121
+
122
+ /**
123
+ * Export the signer as a crypto key pair
124
+ */
125
+ export() {
126
+ return this.keypair
127
+ }
128
+ }
@@ -0,0 +1,133 @@
1
+ /* eslint-disable unicorn/numeric-separators-style */
2
+ import { webcrypto } from 'iso-base/crypto'
3
+ import { u8 } from 'iso-base/utils'
4
+ import { DIDKey } from 'iso-did/key'
5
+ import { spki } from '../spki.js'
6
+
7
+ /**
8
+ * @typedef {import('../types.js').ISigner<CryptoKeyPair>} ISigner
9
+ */
10
+
11
+ /**
12
+ * RSA signer
13
+ *
14
+ * @implements {ISigner}
15
+ */
16
+ export class RSASigner {
17
+ /** @type {Extract<import('iso-did/types').SignatureAlgorithm, "RS256">} */
18
+ static alg = 'RS256'
19
+
20
+ /** @type {Extract<import('iso-did/types').KeyType, "RSA">} */
21
+ static type = 'RSA'
22
+
23
+ // multicodec code for RSA private key
24
+ static code = 0x1305
25
+
26
+ /**
27
+ * @param {Uint8Array} publicKey
28
+ * @param {CryptoKeyPair} keypair
29
+ */
30
+ constructor(publicKey, keypair) {
31
+ this.keypair = keypair
32
+ this.publicKey = publicKey
33
+ this.alg = RSASigner.alg
34
+ this.type = RSASigner.type
35
+ this.code = RSASigner.code
36
+ this.did = DIDKey.fromPublicKey(RSASigner.type, this.publicKey)
37
+ }
38
+
39
+ /**
40
+ * Generate a new signer
41
+ */
42
+ static async generate() {
43
+ const cryptoKeyPair = await webcrypto.subtle.generateKey(
44
+ {
45
+ name: 'RSASSA-PKCS1-v1_5',
46
+ modulusLength: 2048,
47
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
48
+ hash: { name: 'SHA-256' },
49
+ },
50
+
51
+ false,
52
+ ['sign', 'verify']
53
+ )
54
+
55
+ const publicKey = await webcrypto.subtle.exportKey(
56
+ 'spki',
57
+ cryptoKeyPair.publicKey
58
+ )
59
+
60
+ return new RSASigner(spki.decode(u8(publicKey)), cryptoKeyPair)
61
+ }
62
+
63
+ /**
64
+ * Import a signer from a JWK
65
+ *
66
+ * @param {import('../types.js').PrivateKeyRSAJwk} jwk
67
+ */
68
+ static async importJwk(jwk) {
69
+ const privateKey = await webcrypto.subtle.importKey(
70
+ 'jwk',
71
+ jwk,
72
+ {
73
+ name: 'RSASSA-PKCS1-v1_5',
74
+ hash: 'SHA-256',
75
+ },
76
+ false,
77
+ ['sign']
78
+ )
79
+ const publicKeyJWK = await webcrypto.subtle.importKey(
80
+ 'jwk',
81
+ { ...jwk, d: undefined },
82
+ {
83
+ name: 'RSASSA-PKCS1-v1_5',
84
+ hash: 'SHA-256',
85
+ },
86
+ true,
87
+ ['verify']
88
+ )
89
+
90
+ const publicKey = await webcrypto.subtle.exportKey('spki', publicKeyJWK)
91
+
92
+ return new RSASigner(spki.decode(u8(publicKey)), {
93
+ privateKey,
94
+ publicKey: publicKeyJWK,
95
+ })
96
+ }
97
+
98
+ /**
99
+ * Import a signer from a keypair
100
+ *
101
+ * @param {CryptoKeyPair} keypair
102
+ */
103
+ static async import(keypair) {
104
+ const publicKey = await webcrypto.subtle.exportKey(
105
+ 'spki',
106
+ keypair.publicKey
107
+ )
108
+
109
+ return new RSASigner(spki.decode(u8(publicKey)), keypair)
110
+ }
111
+
112
+ /**
113
+ * Sign a message
114
+ *
115
+ * @param {Uint8Array} message
116
+ */
117
+ async sign(message) {
118
+ const buf = await webcrypto.subtle.sign(
119
+ { name: 'RSASSA-PKCS1-v1_5', saltLength: 128 },
120
+ this.keypair.privateKey,
121
+ message
122
+ )
123
+
124
+ return u8(buf)
125
+ }
126
+
127
+ /**
128
+ * Export the signer as a crypto key pair
129
+ */
130
+ export() {
131
+ return this.keypair
132
+ }
133
+ }
package/src/spki.js ADDED
@@ -0,0 +1,39 @@
1
+ import {
2
+ encodeBitString,
3
+ encodeSequence,
4
+ enterSequence,
5
+ readBitString,
6
+ skipSequence,
7
+ } from './asn1.js'
8
+
9
+ /**
10
+ * SPKI
11
+ */
12
+
13
+ const SPKI_PARAMS_ENCODED = new Uint8Array([
14
+ 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0,
15
+ ])
16
+
17
+ /**
18
+ * @param {Uint8Array} key
19
+ */
20
+ function encode(key) {
21
+ return encodeSequence([SPKI_PARAMS_ENCODED, encodeBitString(key)])
22
+ }
23
+
24
+ /**
25
+ * @param {Uint8Array} info
26
+ */
27
+ function decode(info) {
28
+ // go into the top-level SEQUENCE
29
+ const offset = enterSequence(info, 0)
30
+ // skip the header we expect (SKPI_PARAMS_ENCODED)
31
+ const keyOffset = skipSequence(info, offset)
32
+
33
+ // we expect the bitstring next
34
+ return readBitString(info, keyOffset)
35
+ }
36
+ export const spki = {
37
+ encode,
38
+ decode,
39
+ }
package/src/types.ts ADDED
@@ -0,0 +1,75 @@
1
+ import type { DIDKey } from 'iso-did/key'
2
+ import type { SignatureAlgorithm, KeyType } from 'iso-did/types'
3
+
4
+ export interface ISigner<Export extends CryptoKeyPair | string> {
5
+ /**
6
+ * JWT signing algorithm
7
+ */
8
+ alg: SignatureAlgorithm
9
+ /**
10
+ * Keypair type
11
+ */
12
+ type: KeyType
13
+ /**
14
+ * Multicodec identifier for the private key
15
+ */
16
+ code?: number
17
+
18
+ did: DIDKey
19
+ /**
20
+ * Sign a message
21
+ */
22
+ sign: (message: Uint8Array) => Promise<Uint8Array>
23
+
24
+ export: () => Export
25
+ }
26
+
27
+ export interface VerifyInput {
28
+ signature: Uint8Array
29
+ message: Uint8Array
30
+ publicKey: Uint8Array
31
+ }
32
+ export type Verify = (input: VerifyInput) => Promise<boolean>
33
+
34
+ export type Verifier<T extends SignatureAlgorithm> = Record<T, Verify>
35
+
36
+ export type VerifierRegistry<T extends SignatureAlgorithm> = Partial<
37
+ Verifier<T>
38
+ >
39
+
40
+ export interface ResolverVerifyInput extends VerifyInput {
41
+ alg: SignatureAlgorithm
42
+ }
43
+ export type Cache = (parsed: VerifyInput, verify: Verify) => Promise<boolean>
44
+
45
+ export interface IResolver {
46
+ verify: (input: ResolverVerifyInput) => Promise<boolean>
47
+ }
48
+
49
+ export interface ResolverOptions {
50
+ cache?: Cache | boolean | undefined
51
+ }
52
+
53
+ export interface PublicKeyJwk {
54
+ kty: string
55
+ crv: string
56
+ x: string
57
+ y: string
58
+ }
59
+
60
+ export interface PublicKeyRSAJwk {
61
+ kty: string
62
+ n: string
63
+ e: string
64
+ }
65
+
66
+ export interface PrivateKeyRSAJwk extends PublicKeyRSAJwk {
67
+ d: string
68
+ p: string
69
+ q: string
70
+ dp: string
71
+ dq: string
72
+ qi: string
73
+ }
74
+
75
+ export type PrivateKeyJwk = PublicKeyJwk & { d: string }
package/src/utils.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Create web crypto params for ECDSA.
3
+ *
4
+ * @param {import("iso-did/types").SignatureAlgorithm} alg
5
+ * @returns {{name: 'ECDSA', namedCurve: 'P-256' | 'P-384' | 'P-521', hash: 'SHA-256' | 'SHA-384' | 'SHA-512'}}
6
+ */
7
+ export function createEcdsaParams(alg) {
8
+ switch (alg) {
9
+ case 'ES256': {
10
+ return {
11
+ name: 'ECDSA',
12
+ namedCurve: 'P-256',
13
+ hash: 'SHA-256',
14
+ }
15
+ }
16
+ case 'ES384': {
17
+ return {
18
+ name: 'ECDSA',
19
+ namedCurve: 'P-384',
20
+ hash: 'SHA-384',
21
+ }
22
+ }
23
+ case 'ES512': {
24
+ return {
25
+ name: 'ECDSA',
26
+ namedCurve: 'P-521',
27
+ hash: 'SHA-512',
28
+ }
29
+ }
30
+
31
+ default: {
32
+ throw new TypeError(`Unsupported algorithm ${alg}`)
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,56 @@
1
+ /* eslint-disable unicorn/numeric-separators-style */
2
+ import { webcrypto } from 'iso-base/crypto'
3
+ import { decompress, isCompressed } from 'iso-base/ec-compression'
4
+ import { createEcdsaParams } from '../utils.js'
5
+
6
+ /**
7
+ *
8
+ * @param {import('iso-did/types').SignatureAlgorithm} type
9
+ */
10
+ function createVerifier(type) {
11
+ const params = createEcdsaParams(type)
12
+
13
+ /** @type {import('../types.js').Verify} */
14
+ async function fn({ signature, message, publicKey }) {
15
+ if (isCompressed(publicKey)) {
16
+ publicKey = decompress(publicKey, params.namedCurve)
17
+ }
18
+
19
+ const key = await webcrypto.subtle.importKey(
20
+ 'raw',
21
+ publicKey,
22
+ { name: params.name, namedCurve: params.namedCurve },
23
+ true,
24
+ ['verify']
25
+ )
26
+
27
+ return await webcrypto.subtle.verify(
28
+ {
29
+ name: params.name,
30
+ hash: { name: params.hash },
31
+ },
32
+ key,
33
+ signature,
34
+ message
35
+ )
36
+ }
37
+
38
+ return fn
39
+ }
40
+
41
+ /** @type {import('../types.js').Verifier<'ES256' | 'ES384' | 'ES512'>} */
42
+ export const verifier = {
43
+ ES256: createVerifier('ES256'),
44
+ ES384: createVerifier('ES384'),
45
+ ES512: createVerifier('ES512'),
46
+ }
47
+
48
+ /**
49
+ *
50
+ * @param {'ES256' | 'ES384' | 'ES512'} type
51
+ * @param {import('../types.js').VerifyInput} param1
52
+ * @returns
53
+ */
54
+ export async function verify(type, { signature, message, publicKey }) {
55
+ return await verifier[type]({ signature, message, publicKey })
56
+ }
@@ -0,0 +1,12 @@
1
+ /* eslint-disable unicorn/numeric-separators-style */
2
+ import { verifyAsync } from '@noble/ed25519'
3
+
4
+ /** @type {import('../types').Verify} */
5
+ export function verify({ signature, message, publicKey }) {
6
+ return verifyAsync(signature, message, publicKey)
7
+ }
8
+
9
+ /** @type {import('../types').Verifier<'EdDSA'>} */
10
+ export const verifier = {
11
+ EdDSA: verify,
12
+ }