-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkeygen.js
More file actions
150 lines (123 loc) · 3.3 KB
/
Copy pathkeygen.js
File metadata and controls
150 lines (123 loc) · 3.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
export async function decode(options = {}) {
const { licenseFile } = options
if (!licenseFile) {
throw new Error('license file is required')
}
const encodedPayload = licenseFile.replace(/-----(?:BEGIN|END) LICENSE FILE-----\n?/g, '')
let decodedPayload
try {
decodedPayload = atob(encodedPayload)
} catch (e) {
console.error(e)
throw new Error('failed to decode license file')
}
let payload
try {
payload = JSON.parse(decodedPayload)
} catch (e) {
console.error(e)
throw new Error('failed to parse license file')
}
const { enc, sig, alg } = payload
if (alg !== 'aes-256-gcm+ed25519') {
throw new Error(`license file algorithm is not supported: ${alg}`)
}
return { enc, sig, alg }
}
export async function verify(options = {}) {
const { publicKey, enc, sig } = options
if (!publicKey) {
throw new Error('public key must be a hex-encoded Ed25519 verify key')
}
const publicKeyBytes = new Uint8Array(publicKey.match(/.{2}/g).map(byte => parseInt(byte, 16)))
const signatureBytes = Uint8Array.from(atob(sig), c => c.charCodeAt(0))
const dataBytes = new TextEncoder().encode(`license/${enc}`)
let cryptoKey
try {
cryptoKey = await crypto.subtle.importKey(
'raw',
publicKeyBytes,
{ name: 'Ed25519', public: true },
true,
['verify'],
)
} catch (e) {
console.error(e)
throw new Error('failed to import public key')
}
let isValid = false
try {
isValid = await crypto.subtle.verify(
{ name: 'Ed25519' },
cryptoKey,
signatureBytes,
dataBytes,
)
} catch (e) {
console.error(e)
throw new Error('failed to verify license file signature')
}
if (!isValid) {
throw new Error('license file signature is invalid')
}
return true
}
export async function decrypt(options = {}) {
const { enc, licenseKey } = options
if (!licenseKey) {
throw new Error('license file is required')
}
const [ciphertext, iv, tag] = enc.split('.')
let digest
try {
digest = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(licenseKey),
)
} catch (e) {
console.error(e)
throw new Error('failed to generate license key digest')
}
let aesKey
try {
aesKey = await crypto.subtle.importKey(
'raw',
digest,
{ name: 'AES-GCM', length: 256 },
false,
['decrypt'],
)
} catch (e) {
console.error(e)
throw new Error('failed to import license key digest')
}
let decrypted
try {
decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: Uint8Array.from(atob(iv), c => c.charCodeAt(0)),
additionalData: new Uint8Array(),
tagLength: 128
},
aesKey,
Uint8Array.from(
atob(ciphertext) + atob(tag),
c => c.charCodeAt(0),
),
)
} catch (e) {
console.error(e)
throw new Error('failed to decrypt license file')
}
const plaintext = new TextDecoder().decode(decrypted)
const { meta, data, included } = JSON.parse(plaintext)
const { issued, expiry } = meta
if (new Date(issued).getTime() > Date.now()) {
throw new Error('system clock is desynced')
}
if (expiry && (new Date(expiry).getTime() < Date.now())) {
throw new Error('license file is expired')
}
return { meta, data, included }
}