@twin.org/web 0.0.1-next.38 → 0.0.1-next.39

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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  var core = require('@twin.org/core');
4
4
  var jose = require('jose');
5
+ var crypto = require('@twin.org/crypto');
5
6
 
6
7
  // Copyright 2024 IOTA Stiftung.
7
8
  // SPDX-License-Identifier: Apache-2.0.
@@ -846,21 +847,12 @@ class Jwt {
846
847
  core.Guards.defined(Jwt._CLASS_NAME, "key", key);
847
848
  const signer = new jose.SignJWT(payload);
848
849
  signer.setProtectedHeader(header);
850
+ let finalKey = key;
849
851
  if (header.alg === "EdDSA" && core.Is.uint8Array(key)) {
850
- // crypto.subtle.importKey does not support Ed25519 keys in raw format.
851
- // We need to convert the key to PKCS8 format before importing.
852
- // The PKCS8 format is the raw key prefixed with the ASN.1 sequence for an Ed25519 private key.
853
- // The ASN.1 sequence is 48 46 02 01 00 30 05 06 03 2b 65 70 04 20 04 20
854
- const pkcs8Prefix = new Uint8Array([
855
- 48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32
856
- ]); // 0x302e020100300506032b657004220420
857
- const pkcs8PrivateKey = core.Uint8ArrayHelper.concat([pkcs8Prefix, key]);
858
- const imported = await crypto.subtle.importKey("pkcs8", pkcs8PrivateKey, "Ed25519", false, [
859
- "sign"
860
- ]);
861
- return signer.sign(imported);
852
+ // Jose does not support Ed25519 keys in raw format, so we need to convert it to PKCS8.
853
+ finalKey = await crypto.Ed25519.privateKeyToPKCS8(key);
862
854
  }
863
- return signer.sign(key);
855
+ return signer.sign(finalKey);
864
856
  }
865
857
  /**
866
858
  * The default verifier for the JWT.
@@ -888,7 +880,9 @@ class Jwt {
888
880
  * @param payload The payload.
889
881
  * @returns The bytes to sign.
890
882
  */
891
- static createSignBytes(header, payload) {
883
+ static toSigningBytes(header, payload) {
884
+ core.Guards.object(Jwt._CLASS_NAME, "header", header);
885
+ core.Guards.object(Jwt._CLASS_NAME, "payload", payload);
892
886
  const segments = [];
893
887
  const headerBytes = core.Converter.utf8ToBytes(JSON.stringify(header));
894
888
  segments.push(core.Converter.bytesToBase64Url(headerBytes));
@@ -897,16 +891,56 @@ class Jwt {
897
891
  return core.Converter.utf8ToBytes(segments.join("."));
898
892
  }
899
893
  /**
900
- * Create token from bytes and signature.
901
- * @param signedBytes The signed bytes.
894
+ * Create header and payload from signing bytes.
895
+ * @param signingBytes The signing bytes from a token.
896
+ * @returns The header and payload.
897
+ * @throws If the signing bytes are invalid
898
+ */
899
+ static fromSigningBytes(signingBytes) {
900
+ core.Guards.uint8Array(Jwt._CLASS_NAME, "signingBytes", signingBytes);
901
+ const segments = core.Converter.bytesToUtf8(signingBytes).split(".");
902
+ if (segments.length !== 2) {
903
+ throw new core.GeneralError(Jwt._CLASS_NAME, "invalidSigningBytes");
904
+ }
905
+ const headerBytes = core.Converter.base64UrlToBytes(segments[0]);
906
+ const payloadBytes = core.Converter.base64UrlToBytes(segments[1]);
907
+ return {
908
+ header: core.ObjectHelper.fromBytes(headerBytes),
909
+ payload: core.ObjectHelper.fromBytes(payloadBytes)
910
+ };
911
+ }
912
+ /**
913
+ * Convert signed bytes and signature bytes to token.
914
+ * @param signingBytes The signed bytes.
902
915
  * @param signature The signature.
903
916
  * @returns The token.
904
917
  */
905
- static createTokenFromBytes(signedBytes, signature) {
906
- const signedBytesUtf8 = core.Converter.bytesToUtf8(signedBytes);
918
+ static tokenFromBytes(signingBytes, signature) {
919
+ core.Guards.uint8Array(Jwt._CLASS_NAME, "signingBytes", signingBytes);
920
+ core.Guards.uint8Array(Jwt._CLASS_NAME, "signature", signature);
921
+ const signedBytesUtf8 = core.Converter.bytesToUtf8(signingBytes);
907
922
  const signatureBase64 = core.Converter.bytesToBase64Url(signature);
908
923
  return `${signedBytesUtf8}.${signatureBase64}`;
909
924
  }
925
+ /**
926
+ * Convert the token to signing bytes and signature bytes.
927
+ * @param token The token to convert to bytes.
928
+ * @returns The decoded bytes.
929
+ * @throws If the token is invalid.
930
+ */
931
+ static tokenToBytes(token) {
932
+ core.Guards.stringValue(Jwt._CLASS_NAME, "token", token);
933
+ const segments = token.split(".");
934
+ if (segments.length !== 3) {
935
+ throw new core.GeneralError(Jwt._CLASS_NAME, "invalidTokenParts");
936
+ }
937
+ const signingBytes = core.Converter.utf8ToBytes(`${segments[0]}.${segments[1]}`);
938
+ const signature = core.Converter.base64UrlToBytes(segments[2]);
939
+ return {
940
+ signingBytes,
941
+ signature
942
+ };
943
+ }
910
944
  /**
911
945
  * Encode a token.
912
946
  * @param header The header to encode.
@@ -1,5 +1,6 @@
1
- import { BaseError, StringHelper, Guards, Is, AsyncCache, ObjectHelper, GeneralError, Converter, Uint8ArrayHelper } from '@twin.org/core';
1
+ import { BaseError, StringHelper, Guards, Is, AsyncCache, ObjectHelper, GeneralError, Converter } from '@twin.org/core';
2
2
  import { importJWK, SignJWT, jwtVerify } from 'jose';
3
+ import { Ed25519 } from '@twin.org/crypto';
3
4
 
4
5
  // Copyright 2024 IOTA Stiftung.
5
6
  // SPDX-License-Identifier: Apache-2.0.
@@ -844,21 +845,12 @@ class Jwt {
844
845
  Guards.defined(Jwt._CLASS_NAME, "key", key);
845
846
  const signer = new SignJWT(payload);
846
847
  signer.setProtectedHeader(header);
848
+ let finalKey = key;
847
849
  if (header.alg === "EdDSA" && Is.uint8Array(key)) {
848
- // crypto.subtle.importKey does not support Ed25519 keys in raw format.
849
- // We need to convert the key to PKCS8 format before importing.
850
- // The PKCS8 format is the raw key prefixed with the ASN.1 sequence for an Ed25519 private key.
851
- // The ASN.1 sequence is 48 46 02 01 00 30 05 06 03 2b 65 70 04 20 04 20
852
- const pkcs8Prefix = new Uint8Array([
853
- 48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32
854
- ]); // 0x302e020100300506032b657004220420
855
- const pkcs8PrivateKey = Uint8ArrayHelper.concat([pkcs8Prefix, key]);
856
- const imported = await crypto.subtle.importKey("pkcs8", pkcs8PrivateKey, "Ed25519", false, [
857
- "sign"
858
- ]);
859
- return signer.sign(imported);
850
+ // Jose does not support Ed25519 keys in raw format, so we need to convert it to PKCS8.
851
+ finalKey = await Ed25519.privateKeyToPKCS8(key);
860
852
  }
861
- return signer.sign(key);
853
+ return signer.sign(finalKey);
862
854
  }
863
855
  /**
864
856
  * The default verifier for the JWT.
@@ -886,7 +878,9 @@ class Jwt {
886
878
  * @param payload The payload.
887
879
  * @returns The bytes to sign.
888
880
  */
889
- static createSignBytes(header, payload) {
881
+ static toSigningBytes(header, payload) {
882
+ Guards.object(Jwt._CLASS_NAME, "header", header);
883
+ Guards.object(Jwt._CLASS_NAME, "payload", payload);
890
884
  const segments = [];
891
885
  const headerBytes = Converter.utf8ToBytes(JSON.stringify(header));
892
886
  segments.push(Converter.bytesToBase64Url(headerBytes));
@@ -895,16 +889,56 @@ class Jwt {
895
889
  return Converter.utf8ToBytes(segments.join("."));
896
890
  }
897
891
  /**
898
- * Create token from bytes and signature.
899
- * @param signedBytes The signed bytes.
892
+ * Create header and payload from signing bytes.
893
+ * @param signingBytes The signing bytes from a token.
894
+ * @returns The header and payload.
895
+ * @throws If the signing bytes are invalid
896
+ */
897
+ static fromSigningBytes(signingBytes) {
898
+ Guards.uint8Array(Jwt._CLASS_NAME, "signingBytes", signingBytes);
899
+ const segments = Converter.bytesToUtf8(signingBytes).split(".");
900
+ if (segments.length !== 2) {
901
+ throw new GeneralError(Jwt._CLASS_NAME, "invalidSigningBytes");
902
+ }
903
+ const headerBytes = Converter.base64UrlToBytes(segments[0]);
904
+ const payloadBytes = Converter.base64UrlToBytes(segments[1]);
905
+ return {
906
+ header: ObjectHelper.fromBytes(headerBytes),
907
+ payload: ObjectHelper.fromBytes(payloadBytes)
908
+ };
909
+ }
910
+ /**
911
+ * Convert signed bytes and signature bytes to token.
912
+ * @param signingBytes The signed bytes.
900
913
  * @param signature The signature.
901
914
  * @returns The token.
902
915
  */
903
- static createTokenFromBytes(signedBytes, signature) {
904
- const signedBytesUtf8 = Converter.bytesToUtf8(signedBytes);
916
+ static tokenFromBytes(signingBytes, signature) {
917
+ Guards.uint8Array(Jwt._CLASS_NAME, "signingBytes", signingBytes);
918
+ Guards.uint8Array(Jwt._CLASS_NAME, "signature", signature);
919
+ const signedBytesUtf8 = Converter.bytesToUtf8(signingBytes);
905
920
  const signatureBase64 = Converter.bytesToBase64Url(signature);
906
921
  return `${signedBytesUtf8}.${signatureBase64}`;
907
922
  }
923
+ /**
924
+ * Convert the token to signing bytes and signature bytes.
925
+ * @param token The token to convert to bytes.
926
+ * @returns The decoded bytes.
927
+ * @throws If the token is invalid.
928
+ */
929
+ static tokenToBytes(token) {
930
+ Guards.stringValue(Jwt._CLASS_NAME, "token", token);
931
+ const segments = token.split(".");
932
+ if (segments.length !== 3) {
933
+ throw new GeneralError(Jwt._CLASS_NAME, "invalidTokenParts");
934
+ }
935
+ const signingBytes = Converter.utf8ToBytes(`${segments[0]}.${segments[1]}`);
936
+ const signature = Converter.base64UrlToBytes(segments[2]);
937
+ return {
938
+ signingBytes,
939
+ signature
940
+ };
941
+ }
908
942
  /**
909
943
  * Encode a token.
910
944
  * @param header The header to encode.
@@ -92,12 +92,32 @@ export declare class Jwt {
92
92
  * @param payload The payload.
93
93
  * @returns The bytes to sign.
94
94
  */
95
- static createSignBytes<T extends IJwtHeader, U extends IJwtPayload>(header: T, payload: U): Uint8Array;
95
+ static toSigningBytes<T extends IJwtHeader, U extends IJwtPayload>(header: T, payload: U): Uint8Array;
96
96
  /**
97
- * Create token from bytes and signature.
98
- * @param signedBytes The signed bytes.
97
+ * Create header and payload from signing bytes.
98
+ * @param signingBytes The signing bytes from a token.
99
+ * @returns The header and payload.
100
+ * @throws If the signing bytes are invalid
101
+ */
102
+ static fromSigningBytes<T extends IJwtHeader, U extends IJwtPayload>(signingBytes: Uint8Array): {
103
+ header: T;
104
+ payload: U;
105
+ };
106
+ /**
107
+ * Convert signed bytes and signature bytes to token.
108
+ * @param signingBytes The signed bytes.
99
109
  * @param signature The signature.
100
110
  * @returns The token.
101
111
  */
102
- static createTokenFromBytes(signedBytes: Uint8Array, signature: Uint8Array): string;
112
+ static tokenFromBytes(signingBytes: Uint8Array, signature: Uint8Array): string;
113
+ /**
114
+ * Convert the token to signing bytes and signature bytes.
115
+ * @param token The token to convert to bytes.
116
+ * @returns The decoded bytes.
117
+ * @throws If the token is invalid.
118
+ */
119
+ static tokenToBytes(token: string): {
120
+ signingBytes: Uint8Array;
121
+ signature: Uint8Array;
122
+ };
103
123
  }
package/docs/changelog.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # @twin.org/web - Changelog
2
2
 
3
- ## 0.0.1-next.38
3
+ ## 0.0.1-next.39
4
4
 
5
5
  - Initial Release
@@ -298,9 +298,9 @@ True if the signature was verified.
298
298
 
299
299
  ***
300
300
 
301
- ### createSignBytes()
301
+ ### toSigningBytes()
302
302
 
303
- > `static` **createSignBytes**\<`T`, `U`\>(`header`, `payload`): `Uint8Array`
303
+ > `static` **toSigningBytes**\<`T`, `U`\>(`header`, `payload`): `Uint8Array`
304
304
 
305
305
  Create bytes for signing from header and payload.
306
306
 
@@ -332,15 +332,55 @@ The bytes to sign.
332
332
 
333
333
  ***
334
334
 
335
- ### createTokenFromBytes()
335
+ ### fromSigningBytes()
336
336
 
337
- > `static` **createTokenFromBytes**(`signedBytes`, `signature`): `string`
337
+ > `static` **fromSigningBytes**\<`T`, `U`\>(`signingBytes`): `object`
338
338
 
339
- Create token from bytes and signature.
339
+ Create header and payload from signing bytes.
340
+
341
+ #### Type Parameters
342
+
343
+ • **T** *extends* [`IJwtHeader`](../interfaces/IJwtHeader.md)
344
+
345
+ • **U** *extends* [`IJwtPayload`](../interfaces/IJwtPayload.md)
346
+
347
+ #### Parameters
348
+
349
+ ##### signingBytes
350
+
351
+ `Uint8Array`
352
+
353
+ The signing bytes from a token.
354
+
355
+ #### Returns
356
+
357
+ `object`
358
+
359
+ The header and payload.
360
+
361
+ ##### header
362
+
363
+ > **header**: `T`
364
+
365
+ ##### payload
366
+
367
+ > **payload**: `U`
368
+
369
+ #### Throws
370
+
371
+ If the signing bytes are invalid
372
+
373
+ ***
374
+
375
+ ### tokenFromBytes()
376
+
377
+ > `static` **tokenFromBytes**(`signingBytes`, `signature`): `string`
378
+
379
+ Convert signed bytes and signature bytes to token.
340
380
 
341
381
  #### Parameters
342
382
 
343
- ##### signedBytes
383
+ ##### signingBytes
344
384
 
345
385
  `Uint8Array`
346
386
 
@@ -357,3 +397,37 @@ The signature.
357
397
  `string`
358
398
 
359
399
  The token.
400
+
401
+ ***
402
+
403
+ ### tokenToBytes()
404
+
405
+ > `static` **tokenToBytes**(`token`): `object`
406
+
407
+ Convert the token to signing bytes and signature bytes.
408
+
409
+ #### Parameters
410
+
411
+ ##### token
412
+
413
+ `string`
414
+
415
+ The token to convert to bytes.
416
+
417
+ #### Returns
418
+
419
+ `object`
420
+
421
+ The decoded bytes.
422
+
423
+ ##### signingBytes
424
+
425
+ > **signingBytes**: `Uint8Array`
426
+
427
+ ##### signature
428
+
429
+ > **signature**: `Uint8Array`
430
+
431
+ #### Throws
432
+
433
+ If the token is invalid.
package/locales/en.json CHANGED
@@ -10,7 +10,9 @@
10
10
  "jwt": {
11
11
  "noKeyOrSigner": "No key or signer was provided for JWT creation",
12
12
  "noKeyOrVerifier": "No key or verifier was provided for JWT creation",
13
- "verifyFailed": "Failed to verify JWT"
13
+ "verifyFailed": "Failed to verify JWT",
14
+ "invalidTokenParts": "The JSON Web Token could not be parsed, it should contain three parts separated by dots",
15
+ "invalidSigningBytes": "The signing bytes are invalid, it should contain two parts separated by a dot"
14
16
  },
15
17
  "jwk": {
16
18
  "jwkImportFailed": "Failed to import JWK"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/web",
3
- "version": "0.0.1-next.38",
3
+ "version": "0.0.1-next.39",
4
4
  "description": "Contains classes for use with web operations",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,8 +14,8 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/core": "0.0.1-next.38",
18
- "@twin.org/crypto": "0.0.1-next.38",
17
+ "@twin.org/core": "0.0.1-next.39",
18
+ "@twin.org/crypto": "0.0.1-next.39",
19
19
  "@twin.org/nameof": "next",
20
20
  "jose": "6.0.8"
21
21
  },