@gcoredev/as-jwt 1.0.0 → 1.0.2

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # as-jwt
2
2
 
3
+ [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/G-Core/as-jwt/deploy.yaml)](https://github.com/G-Core/as-jwt)
4
+ [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/G-Core/as-jwt)](https://github.com/G-Core/as-jwt)
5
+ [![GitHub top language](https://img.shields.io/github/languages/top/G-Core/as-jwt)](https://github.com/G-Core/as-jwt)
6
+ [![GitHub License](https://img.shields.io/github/license/G-Core/as-jwt)](https://github.com/G-Core/as-jwt/blob/main/LICENSE)
7
+ [![NPM Version](https://img.shields.io/npm/v/@gcoredev/as-jwt)](https://www.npmjs.com/package/@gcoredev/as-jwt)
8
+
3
9
  AssemblyScript package that provides simple jws handling for jwt tokens.
4
10
 
5
11
  ## Installation:
@@ -43,43 +49,10 @@ Validates `token` signature has been signed with a valid `secret`.
43
49
 
44
50
  Does NOT validate any claims in the payload.
45
51
 
46
- ## Known Issues
47
-
48
- At present there are some issues with JWS tokens signed using SHA256.
49
-
50
- A basic token containing:
51
-
52
- ```json
53
- {
54
- "alg": "HS256",
55
- "typ": "JWT"
56
- }.{
57
- "sub": "1234567890",
58
- "name": "John Doe",
59
- "iat": 1516239022
60
- }
61
- ```
62
-
63
- Will `pass` compactVerify(), however adding an extra field like `exp` value will cause it to fail. Claim payloads with more than 3 entries fails `SHA256` hashing. This is **NOT** a problem with `SHA512` and we would advise to use this at present.
64
-
65
- It is possible to still use verifyJwt() with `SHA256` however you will need to keep to max 3 claims. e.g.
66
-
67
- ```json
68
- {
69
- "alg": "HS256",
70
- "typ": "JWT"
71
- }.{
72
- "exp": 2051226061,
73
- "name": "John Doe",
74
- "iat": 1516239022
75
- }
76
- ```
77
-
78
- Issue: https://github.com/jedisct1/as-hmac-sha2/issues/4
79
-
80
52
  ### Internal Libraries:
81
53
 
82
54
  Under the hood this package is powered by:
83
55
 
84
56
  - [as-hmac-sha2](https://github.com/jedisct1/as-hmac-sha2)
85
57
  - [as-base64](https://github.com/near/as-base64)
58
+ - [as-sha256](https://github.com/ChainSafe/as-sha256)
package/assembly/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { JSON } from "assemblyscript-json/assembly";
2
2
 
3
- import { Sha256, Sha512, verify } from "../modules/as-hmac-sha2/assembly";
4
- import { decodeBase64 } from "./utils";
3
+ import { Sha512, verify } from "../modules/as-hmac-sha2/assembly";
4
+
5
+ import { sha256Hmac } from "./sha256";
6
+ import { decodeBase64, isValidJsonObj } from "./utils";
5
7
 
6
8
  enum JwtValidation {
7
9
  Ok = 0,
@@ -19,9 +21,12 @@ function compactVerify(token: string, secret: string): JwtValidation {
19
21
 
20
22
  // Decode the JWT token
21
23
  const header = decodeBase64(parts[0]);
22
- const jsonHeaderObj: JSON.Obj = <JSON.Obj>(
23
- JSON.parse(String.UTF8.decode(header.buffer))
24
- );
24
+ const headerStr = String.UTF8.decode(header.buffer);
25
+ if (!isValidJsonObj(headerStr)) {
26
+ return JwtValidation.BadToken;
27
+ }
28
+
29
+ const jsonHeaderObj: JSON.Obj = <JSON.Obj>JSON.parse(headerStr);
25
30
 
26
31
  const algOrNull: JSON.Str | null = jsonHeaderObj.getString("alg");
27
32
  if (algOrNull == null) {
@@ -38,7 +43,7 @@ function compactVerify(token: string, secret: string): JwtValidation {
38
43
  const secretUint8Array = Uint8Array.wrap(String.UTF8.encode(secret));
39
44
  const expectedSignature =
40
45
  alg === "HS256"
41
- ? Sha256.hmac(dataUint8Array, secretUint8Array)
46
+ ? sha256Hmac(dataUint8Array, secretUint8Array)
42
47
  : Sha512.hmac(dataUint8Array, secretUint8Array);
43
48
  const providedSignature = decodeBase64(parts[2]);
44
49
 
@@ -59,9 +64,11 @@ function jwtVerify(token: string, secret: string): JwtValidation {
59
64
 
60
65
  // Decode the JWT token
61
66
  const payload = decodeBase64(parts[1]);
62
- const jsonClaimsObj: JSON.Obj = <JSON.Obj>(
63
- JSON.parse(String.UTF8.decode(payload.buffer))
64
- );
67
+ const payloadStr = String.UTF8.decode(payload.buffer);
68
+ if (!isValidJsonObj(payloadStr)) {
69
+ return JwtValidation.BadToken;
70
+ }
71
+ const jsonClaimsObj: JSON.Obj = <JSON.Obj>JSON.parse(payloadStr);
65
72
 
66
73
  // RFC 7519 states that the exp , nbf and iat claim values must be NumericDate values.
67
74
  const expOrNull: JSON.Integer | null = jsonClaimsObj.getInteger("exp");
@@ -0,0 +1,257 @@
1
+ /*
2
+ * The code in this file is from https://github.com/ChainSafe/as-sha256/blob/master/assembly/index.ts
3
+ * Copyright 2019 ChainSafe Systems
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * All modifcations have been noted within the code.
9
+ *
10
+ */
11
+
12
+ // constants used in the SHA256 compression function
13
+ const K: u32[] = [
14
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
15
+ 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,
16
+ 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,
17
+ 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
18
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,
19
+ 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
20
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
21
+ 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
22
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
23
+ 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,
24
+ 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
25
+ 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
26
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
27
+ ];
28
+ const kPtr = K.dataStart;
29
+
30
+ // intermediate hash values stored in H0-H7
31
+ var H0: u32, H1: u32, H2: u32, H3: u32, H4: u32, H5: u32, H6: u32, H7: u32;
32
+
33
+ // hash registers
34
+ var a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32, h: u32, i: u32, t1: u32, t2: u32;
35
+
36
+ // 16 32bit message blocks
37
+ const M = new ArrayBuffer(64);
38
+ const mPtr = changetype<usize>(M);
39
+
40
+ // 64 32bit extended message blocks
41
+ const W = new ArrayBuffer(256);
42
+ const wPtr = changetype<usize>(W);
43
+
44
+ // number of bytes in M buffer
45
+ var mLength = 0;
46
+
47
+ // number of total bytes hashed
48
+ var bytesHashed = 0;
49
+
50
+ @inline
51
+ function load32(ptr: usize, offset: usize): u32 {
52
+ return load<u32>(ptr + (offset << alignof<u32>()));
53
+ }
54
+
55
+ @inline
56
+ function load32be(ptr: usize, offset: usize): u32 {
57
+ const firstOffset = offset << alignof<u32>();
58
+ return (
59
+ (<u32>load8(ptr, firstOffset + 0) << 24) |
60
+ (<u32>load8(ptr, firstOffset + 1) << 16) |
61
+ (<u32>load8(ptr, firstOffset + 2) << 8) |
62
+ (<u32>load8(ptr, firstOffset + 3) << 0)
63
+ );
64
+ }
65
+
66
+ @inline
67
+ function store32(ptr: usize, offset: usize, u: u32): void {
68
+ store<u32>(ptr + (offset << alignof<u32>()), u);
69
+ }
70
+
71
+ @inline
72
+ function store8(ptr: usize, offset: usize, u: u8): void {
73
+ store<u8>(ptr + offset, u);
74
+ }
75
+
76
+ @inline
77
+ function load8(ptr: usize, offset: usize): u8 {
78
+ return load<u8>(ptr + offset);
79
+ }
80
+
81
+ @inline
82
+ function fill(ptr: usize, value: u8, length: u32): void {
83
+ const finalPtr = ptr + length;
84
+ while(ptr < finalPtr) {
85
+ store<u8>(ptr, value);
86
+ ptr++;
87
+ }
88
+ }
89
+
90
+ @inline
91
+ function CH(x: u32, y: u32, z: u32): u32 {
92
+ return((x & y) ^ (~x & z));
93
+ }
94
+
95
+ @inline
96
+ function MAJ(x: u32, y: u32, z:u32): u32 {
97
+ return ((x & y) ^ (x & z) ^ (y & z));
98
+ }
99
+
100
+ @inline
101
+ function EP0(x: u32): u32 {
102
+ return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
103
+ }
104
+
105
+ @inline
106
+ function EP1(x: u32): u32 {
107
+ return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
108
+ }
109
+
110
+ @inline
111
+ function SIG0(x: u32): u32 {
112
+ return rotr(x, 7) ^ rotr(x, 18) ^ (x >>> 3);
113
+ }
114
+
115
+ @inline
116
+ function SIG1(x: u32): u32 {
117
+ return rotr(x, 17) ^ rotr(x, 19) ^ (x >>> 10);
118
+ }
119
+
120
+ /**
121
+ * Expand message blocks (16 32bit blocks), into extended message blocks (64 32bit blocks),
122
+ * Apply SHA256 compression function on extended message blocks
123
+ * Update intermediate hash values
124
+ * @param wPtr pointer to expanded message block memory
125
+ * @param mPtr pointer to message block memory, pass 0 if wPtr is precomputed for e.g. in digest64
126
+ */
127
+ function hashBlocks(wPtr: usize, mPtr: usize): void {
128
+ a = H0;
129
+ b = H1;
130
+ c = H2;
131
+ d = H3;
132
+ e = H4;
133
+ f = H5;
134
+ g = H6;
135
+ h = H7;
136
+
137
+ // Load message blocks into first 16 expanded message blocks
138
+ for (i = 0; i < 16; i++) {
139
+ store32(wPtr, i, load32be(mPtr, i));
140
+ }
141
+ // Expand message blocks 17-64
142
+ for (i = 16; i < 64; i++) {
143
+ store32(
144
+ wPtr,
145
+ i,
146
+ SIG1(load32(wPtr, i - 2)) +
147
+ load32(wPtr, i - 7) +
148
+ SIG0(load32(wPtr, i - 15)) +
149
+ load32(wPtr, i - 16)
150
+ );
151
+ }
152
+
153
+ // Apply SHA256 compression function on expanded message blocks
154
+ for (i = 0; i < 64; i++) {
155
+ t1 = h + EP1(e) + CH(e, f, g) + load32(kPtr, i) + load32(wPtr, i);
156
+ t2 = EP0(a) + MAJ(a, b, c);
157
+ h = g;
158
+ g = f;
159
+ f = e;
160
+ e = d + t1;
161
+ d = c;
162
+ c = b;
163
+ b = a;
164
+ a = t1 + t2;
165
+ }
166
+
167
+ H0 += a;
168
+ H1 += b;
169
+ H2 += c;
170
+ H3 += d;
171
+ H4 += e;
172
+ H5 += f;
173
+ H6 += g;
174
+ H7 += h;
175
+ }
176
+
177
+ export function init(): void {
178
+ H0 = 0x6a09e667;
179
+ H1 = 0xbb67ae85;
180
+ H2 = 0x3c6ef372;
181
+ H3 = 0xa54ff53a;
182
+ H4 = 0x510e527f;
183
+ H5 = 0x9b05688c;
184
+ H6 = 0x1f83d9ab;
185
+ H7 = 0x5be0cd19;
186
+
187
+ mLength = 0;
188
+ bytesHashed = 0;
189
+ }
190
+
191
+ export function update(dataPtr: usize, dataLength: i32): void {
192
+ let dataPos = 0;
193
+ bytesHashed += dataLength;
194
+ // If message blocks buffer has data, fill to 64
195
+ if (mLength) {
196
+ if (64 - mLength <= dataLength) {
197
+ // we can fully fill the buffer with data left over
198
+ memory.copy(mPtr + mLength, dataPtr, 64 - mLength);
199
+ mLength += 64 - mLength;
200
+ dataPos += 64 - mLength;
201
+ dataLength -= 64 - mLength;
202
+ hashBlocks(wPtr, mPtr);
203
+ mLength = 0;
204
+ } else {
205
+ // we can't fully fill the buffer but we exhaust the whole data buffer
206
+ memory.copy(mPtr + mLength, dataPtr, dataLength);
207
+ mLength += dataLength;
208
+ dataPos += dataLength;
209
+ dataLength -= dataLength;
210
+ return;
211
+ }
212
+ }
213
+ // If input has remaining 64-byte chunks, hash those
214
+ for (let i = 0; i < dataLength / 64; i++, dataPos += 64) {
215
+ hashBlocks(wPtr, dataPtr + dataPos);
216
+ }
217
+ // If any additional bytes remain, copy into message blocks buffer
218
+ if (dataLength & 63) {
219
+ const frac = dataLength & 63;
220
+ memory.copy(mPtr + mLength, dataPtr + dataPos, dataLength & 63);
221
+ mLength += dataLength & 63;
222
+ }
223
+ }
224
+
225
+ export function final(outPtr: usize): void {
226
+ // one additional round of hashes required
227
+ // because padding will not fit
228
+ if ((bytesHashed & 63) <= 63) { //! @gcoredev: This line has been modified from the original (message bodies of length 127 failed)
229
+ store8(mPtr, mLength, 0x80);
230
+ mLength++;
231
+ }
232
+ if ((bytesHashed & 63) >= 56) {
233
+ fill(mPtr + mLength, 0, 64 - mLength);
234
+ hashBlocks(wPtr, mPtr);
235
+ mLength = 0;
236
+ }
237
+ if ((bytesHashed & 63) > 63) { //! @gcoredev: This line has been modified from the original (message bodies of length 127 failed)
238
+ store8(mPtr, mLength, 0x80);
239
+ mLength++;
240
+ }
241
+ fill(mPtr + mLength, 0, 64 - mLength - 8);
242
+
243
+ store<u32>(mPtr + 64 - 8, bswap(bytesHashed / 0x20000000)); // length -- high bits
244
+ store<u32>(mPtr + 64 - 4, bswap(bytesHashed << 3)); // length -- low bits
245
+
246
+ // hash round for padding
247
+ hashBlocks(wPtr, mPtr);
248
+
249
+ store32(outPtr, 0, bswap(H0));
250
+ store32(outPtr, 1, bswap(H1));
251
+ store32(outPtr, 2, bswap(H2));
252
+ store32(outPtr, 3, bswap(H3));
253
+ store32(outPtr, 4, bswap(H4));
254
+ store32(outPtr, 5, bswap(H5));
255
+ store32(outPtr, 6, bswap(H6));
256
+ store32(outPtr, 7, bswap(H7));
257
+ }
@@ -0,0 +1,50 @@
1
+ import { final, init, update } from "./as-sha256";
2
+
3
+ /*
4
+ * The code in this file is largely based on https://github.com/jedisct1/as-hmac-sha2
5
+ * which is licensed under the ISC license.
6
+ *
7
+ */
8
+
9
+ /** This function is a direct copy of the one in as-hmac-sha2 */
10
+ function setU8(t: Uint8Array, s: Uint8Array, o: isize = 0): void {
11
+ memory.copy(t.dataStart + o, s.dataStart, s.length);
12
+ }
13
+
14
+ /**
15
+ * This function is based on the one in as-hmac-sha2, but has been modified to use the crypto functions in as-sha256.
16
+ * HMAC-SHA-256
17
+ * @param m Message
18
+ * @param k Key
19
+ * @returns `HMAC-SHA-256(m, k)`
20
+ */
21
+ function sha256Hmac(m: Uint8Array, k: Uint8Array): Uint8Array {
22
+ if (k.length > 64) {
23
+ // k = Sha256.hash(k); todo: should become??
24
+ // init()
25
+ // update()
26
+ // final()
27
+ }
28
+ let b = new Uint8Array(64);
29
+ setU8(b, k);
30
+ for (let i = 0; i < b.length; ++i) {
31
+ b[i] ^= 0x36;
32
+ }
33
+ let out = new Uint8Array(32);
34
+ init();
35
+ update(b.dataStart, b.length);
36
+ update(m.dataStart, m.length);
37
+ final(out.dataStart);
38
+
39
+ for (let i = 0; i < b.length; ++i) {
40
+ b[i] ^= 0x6a;
41
+ }
42
+
43
+ init();
44
+ update(b.dataStart, b.length);
45
+ update(out.dataStart, out.length);
46
+ final(out.dataStart);
47
+ return out;
48
+ }
49
+
50
+ export { sha256Hmac };
package/assembly/utils.ts CHANGED
@@ -28,4 +28,25 @@ function decodeBase64(input: string): Uint8Array {
28
28
  return decode(base64);
29
29
  }
30
30
 
31
- export { decodeBase64, encodeBase64 };
31
+ function isValidJsonObj(str: string): boolean {
32
+ if (str.charAt(0) !== "{" || str.charAt(str.length - 1) !== "}") {
33
+ return false;
34
+ }
35
+ let brackets = 0;
36
+ let braces = 0;
37
+ for (let i = 0; i < str.length; i++) {
38
+ const char = str.charAt(i);
39
+ if (char === "[") {
40
+ brackets += 1;
41
+ } else if (char === "]") {
42
+ brackets -= 1;
43
+ } else if (char === "{") {
44
+ braces += 1;
45
+ } else if (char === "}") {
46
+ braces -= 1;
47
+ }
48
+ }
49
+ return brackets === 0 && braces === 0;
50
+ }
51
+
52
+ export { decodeBase64, encodeBase64, isValidJsonObj };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcoredev/as-jwt",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "AssemblyScript package that provides simple jws handling for jwt tokens/",
5
5
  "main": "assembly/index.ts",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "registry": "https://registry.npmjs.org/"
31
31
  },
32
32
  "files": [
33
- "assembly/*.*",
33
+ "assembly/**/*.*",
34
34
  "modules/as-base64/assembly",
35
35
  "modules/as-base64/README.md",
36
36
  "modules/as-base64/package.json",