@pagopa/io-react-native-jwt 0.6.3 → 1.0.0

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.
Files changed (56) hide show
  1. package/README.md +32 -36
  2. package/lib/commonjs/algorithms.js +34 -2
  3. package/lib/commonjs/algorithms.js.map +1 -1
  4. package/lib/commonjs/index.js +12 -0
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/jwt/produce.js +8 -2
  7. package/lib/commonjs/jwt/produce.js.map +1 -1
  8. package/lib/commonjs/jwt/sign.js +54 -75
  9. package/lib/commonjs/jwt/sign.js.map +1 -1
  10. package/lib/commonjs/jwt/unsecured.js +2 -2
  11. package/lib/commonjs/jwt/unsecured.js.map +1 -1
  12. package/lib/commonjs/types.js +1 -1
  13. package/lib/commonjs/types.js.map +1 -1
  14. package/lib/commonjs/utils/asn1.js.map +1 -1
  15. package/lib/commonjs/utils/crypto.js +6 -0
  16. package/lib/commonjs/utils/crypto.js.map +1 -0
  17. package/lib/module/algorithms.js +31 -0
  18. package/lib/module/algorithms.js.map +1 -1
  19. package/lib/module/index.js +1 -1
  20. package/lib/module/index.js.map +1 -1
  21. package/lib/module/jwt/produce.js +8 -2
  22. package/lib/module/jwt/produce.js.map +1 -1
  23. package/lib/module/jwt/sign.js +56 -78
  24. package/lib/module/jwt/sign.js.map +1 -1
  25. package/lib/module/jwt/unsecured.js +2 -2
  26. package/lib/module/jwt/unsecured.js.map +1 -1
  27. package/lib/module/types.js +1 -1
  28. package/lib/module/types.js.map +1 -1
  29. package/lib/module/utils/asn1.js.map +1 -1
  30. package/lib/module/utils/crypto.js +2 -0
  31. package/lib/module/utils/crypto.js.map +1 -0
  32. package/lib/typescript/algorithms.d.ts +5 -2
  33. package/lib/typescript/algorithms.d.ts.map +1 -1
  34. package/lib/typescript/index.d.ts +2 -1
  35. package/lib/typescript/index.d.ts.map +1 -1
  36. package/lib/typescript/jwt/produce.d.ts +1 -1
  37. package/lib/typescript/jwt/produce.d.ts.map +1 -1
  38. package/lib/typescript/jwt/sign.d.ts +26 -26
  39. package/lib/typescript/jwt/sign.d.ts.map +1 -1
  40. package/lib/typescript/jwt/unsecured.d.ts +2 -2
  41. package/lib/typescript/jwt/unsecured.d.ts.map +1 -1
  42. package/lib/typescript/types.d.ts +5 -4
  43. package/lib/typescript/types.d.ts.map +1 -1
  44. package/lib/typescript/utils/asn1.d.ts +2 -1
  45. package/lib/typescript/utils/asn1.d.ts.map +1 -1
  46. package/lib/typescript/utils/crypto.d.ts +25 -0
  47. package/lib/typescript/utils/crypto.d.ts.map +1 -0
  48. package/package.json +1 -1
  49. package/src/algorithms.ts +38 -3
  50. package/src/index.ts +7 -1
  51. package/src/jwt/produce.ts +4 -3
  52. package/src/jwt/sign.ts +64 -101
  53. package/src/jwt/unsecured.ts +5 -5
  54. package/src/types.ts +5 -4
  55. package/src/utils/asn1.ts +6 -2
  56. package/src/utils/crypto.ts +25 -0
package/src/index.ts CHANGED
@@ -12,5 +12,11 @@ export { UnsecuredJWT } from './jwt/unsecured';
12
12
  export type { UnsecuredResult } from './jwt/unsecured';
13
13
 
14
14
  export { derToJose } from './utils/asn1';
15
- export { encodeBase64, decodeBase64 } from './utils/base64';
15
+ export {
16
+ encodeBase64,
17
+ decodeBase64,
18
+ removePadding,
19
+ addPadding,
20
+ } from './utils/base64';
16
21
  export { sha256ToBase64 } from './hash';
22
+ export { type CryptoContext } from './utils/crypto';
@@ -4,14 +4,15 @@ import secs, { epoch } from '../utils/secs';
4
4
 
5
5
  /** Generic class for JWT producing. */
6
6
  export class ProduceJWT {
7
- protected _payload!: JWTPayload;
7
+ protected _payload: JWTPayload = {};
8
8
 
9
9
  /** @param payload The JWT Claims Set object. */
10
- constructor(payload: JWTPayload) {
10
+ setPayload(payload: JWTPayload) {
11
11
  if (!isObject(payload)) {
12
12
  throw new TypeError('JWT Claims Set MUST be an object');
13
13
  }
14
- this._payload = payload;
14
+ this._payload = { ...this._payload, ...payload };
15
+ return this;
15
16
  }
16
17
 
17
18
  /**
package/src/jwt/sign.ts CHANGED
@@ -1,14 +1,15 @@
1
- import { JOSENotSupported, JWSInvalid, JWTInvalid } from '../utils/errors';
2
- import type {
3
- CompactJWSHeaderParameters,
4
- JWTDecodeResult,
5
- JWTPayload,
6
- } from '../types';
1
+ import { JOSENotSupported, JWTInvalid } from '../utils/errors';
2
+ import type { JWSHeaderParameters, JWTDecodeResult } from '../types';
7
3
  import { ProduceJWT } from './produce';
8
4
  import { decodeBase64, encodeBase64, removePadding } from '../utils/base64';
9
- import { getKtyFromAlg, isAlgSupported } from '../algorithms';
5
+ import {
6
+ getAlgFromKey,
7
+ isAlgSupported,
8
+ type SupportedAlgorithm,
9
+ } from '../algorithms';
10
10
 
11
11
  import { derToJose } from '../utils/asn1';
12
+ import type { CryptoContext } from 'src/utils/crypto';
12
13
 
13
14
  /**
14
15
  * The SignJWT class is used to build and sign Compact JWS formatted JSON Web Tokens.
@@ -16,134 +17,96 @@ import { derToJose } from '../utils/asn1';
16
17
  * @example Usage with a given signature
17
18
  *
18
19
  * ```js
19
- * const alg = 'RS256'
20
- * const signature = 'Axy0....'
20
+ * const crypto: Crypto = { any implementation fo Crypto interface }
21
21
  *
22
- * const jwt = new SignJWT({ 'urn:example:claim': true })
23
- * .setProtectedHeader({ alg })
22
+ * const jwt = new SignJWT(crypto)
23
+ * .setPayload({ 'urn:example:claim': true })
24
+ * .setProtectedHeader({ typ })
24
25
  * .setIssuedAt()
25
26
  * .setIssuer('urn:example:issuer')
26
27
  * .setAudience('urn:example:audience')
27
28
  * .setExpirationTime('2h')
28
- * .sign(signature)
29
29
  *
30
- * console.log(jwt)
30
+ * jwt.data()
31
+ * await jwt.sign()
31
32
  * ```
32
33
  */
34
+
33
35
  export class SignJWT extends ProduceJWT {
34
- private _protectedHeader!: CompactJWSHeaderParameters;
36
+ private _protectedHeader: Omit<JWSHeaderParameters, 'alg'> = {};
37
+ private crypto: CryptoContext;
35
38
 
36
39
  /**
37
- * Sets the JWS Protected Header on the SignJWT object.
38
- *
39
- * @param protectedHeader JWS Protected Header. Must contain an "alg" (JWS Algorithm) property.
40
+ * @param crypto An implementation of the crypto interface. @see utils/crypto for more.
40
41
  */
41
- setProtectedHeader(protectedHeader: CompactJWSHeaderParameters) {
42
- if (protectedHeader.alg && isAlgSupported(protectedHeader.alg)) {
43
- this._protectedHeader = protectedHeader;
44
- return this;
45
- }
46
- throw new JOSENotSupported('Unsupported "alg" value');
42
+ constructor(crypto: CryptoContext) {
43
+ super();
44
+ this.crypto = crypto;
47
45
  }
48
46
 
49
47
  /**
50
- * Return a JWT without signature (`header.payload`) to sign.
48
+ * Sets the JWS Protected Header on the SignJWT object.
49
+ * Any invocation overrides what's been already put in the header.
50
+ * Value for "alg" are ignored, as it will be inferred by the key used by the cryptographic context.
51
51
  *
52
+ * @param protectedHeader JWS Protected Header.
52
53
  */
53
- toSign(): string {
54
- if (!this._protectedHeader || !this._protectedHeader.alg) {
55
- throw new JWSInvalid(
56
- 'Missing signature algorithm. Specify the `alg` field'
57
- );
58
- }
59
- const protectedHeader = encodeBase64(JSON.stringify(this._protectedHeader));
60
- const payload = encodeBase64(JSON.stringify(this._payload));
61
- if (payload && protectedHeader) {
62
- return `${protectedHeader}.${payload}`;
63
- } else {
64
- throw new JWSInvalid();
65
- }
54
+ setProtectedHeader(protectedHeader: Omit<JWSHeaderParameters, 'alg'>) {
55
+ this._protectedHeader = protectedHeader;
56
+ return this;
66
57
  }
67
58
 
68
59
  /**
69
- * Append signature to unsigned JWT.
70
- * For an ECDSA signature it is required that this is in ASN.1/DER encoded format.
71
- * The same format used by the TEE. Conversion to JWS is handled automatically.
60
+ * Return a JWT without signature (`header.payload`).
72
61
  *
73
- * @param jwtWithoutSignature
74
- * @param signature
75
62
  */
76
- static async appendSignature(
77
- jwtWithoutSignature: string,
78
- signature: string
79
- ): Promise<string> {
80
- if (
81
- typeof jwtWithoutSignature !== 'string' ||
82
- typeof signature !== 'string'
83
- ) {
84
- throw new JWTInvalid('JWS must be a string');
85
- }
86
- if (signature === '') throw new JWSInvalid('Invalid signature');
87
-
88
- const jwtDecoded = SignJWT.decodeJwtWithoutSignature(jwtWithoutSignature);
89
- const alg = jwtDecoded.header.alg;
90
- const kty = getKtyFromAlg(alg);
91
-
92
- if (kty === 'EC') {
93
- const encodedJws = await derToJose(signature, alg);
94
- return `${jwtWithoutSignature}.${encodedJws}`;
95
- } else {
96
- const encodedJws = removePadding(signature);
97
- return `${jwtWithoutSignature}.${encodedJws}`;
98
- }
63
+ private async unsigned(): Promise<string> {
64
+ const { alg } = await this.getSelectedSigningAlgorithm();
65
+
66
+ return [{ ...this._protectedHeader, alg }, this._payload]
67
+ .map((e) => JSON.stringify(e))
68
+ .map(encodeBase64)
69
+ .join('.');
70
+ }
71
+
72
+ private async getSelectedSigningAlgorithm(): Promise<{
73
+ kty: string;
74
+ alg: SupportedAlgorithm;
75
+ }> {
76
+ const publicKey = await this.crypto.getPublicKey();
77
+ const alg = getAlgFromKey(publicKey);
78
+
79
+ return { kty: publicKey.kty, alg };
99
80
  }
100
81
 
101
82
  /**
102
- * Decodes a JWT without signature
83
+ * Return a signed JWT.
103
84
  *
104
- * @param jwtWithoutSignature JWT to sign that needs to be decoded.
105
85
  */
106
- static decodeJwtWithoutSignature(jwtWithoutSignature: string): {
107
- payload: JWTPayload;
108
- header: CompactJWSHeaderParameters;
109
- } {
110
- if (typeof jwtWithoutSignature !== 'string') {
111
- throw new JWTInvalid('JWT must be a string');
112
- }
113
- const {
114
- 0: encodedHeader,
115
- 1: encodedPayload,
116
- length,
117
- } = jwtWithoutSignature.split('.');
86
+ async sign(): Promise<string> {
87
+ const unsigned = await this.unsigned();
88
+ const signature = await this.crypto.getSignature(unsigned);
118
89
 
119
- let header: CompactJWSHeaderParameters;
120
- try {
121
- let decoded = decodeBase64(encodedHeader!);
122
- header = JSON.parse(decoded);
123
- if (!header.alg || !isAlgSupported(header.alg)) {
124
- throw new JOSENotSupported('Unsupported "alg" value');
125
- }
126
- } catch {
127
- throw new JWTInvalid('Unable to decode JWT header');
128
- }
90
+ const { kty, alg } = await this.getSelectedSigningAlgorithm();
129
91
 
130
- if (length !== 2 || encodedPayload === '') {
131
- throw new JWTInvalid('Invalid JWT to sign');
132
- }
92
+ const encodedJws = await (kty === 'EC'
93
+ ? derToJose(signature, alg)
94
+ : removePadding(signature));
133
95
 
134
- let decodedPayload = decodeBase64(encodedPayload!);
135
- const payload = JSON.parse(decodedPayload);
96
+ return `${unsigned}.${encodedJws}`;
97
+ }
136
98
 
137
- return {
138
- payload,
139
- header,
140
- };
99
+ /**
100
+ * Plain data of the produced JWT
101
+ */
102
+ data(): JWTDecodeResult {
103
+ return { protectedHeader: this._protectedHeader, payload: this._payload };
141
104
  }
142
105
 
143
106
  /**
144
- * Decodes a JWT without signature
107
+ * Decodes a signed JWT
145
108
  *
146
- * @param jwtWithoutSignature JWT to sign that needs to be decoded.
109
+ * @param jwt JWT to sign that needs to be decoded.
147
110
  */
148
111
  static decode(jwt: string): JWTDecodeResult {
149
112
  if (typeof jwt !== 'string') {
@@ -156,7 +119,7 @@ export class SignJWT extends ProduceJWT {
156
119
  length,
157
120
  } = jwt.split('.');
158
121
 
159
- let protectedHeader: CompactJWSHeaderParameters;
122
+ let protectedHeader: JWSHeaderParameters;
160
123
  try {
161
124
  let decoded = decodeBase64(encodedHeader!);
162
125
  protectedHeader = JSON.parse(decoded);
@@ -1,7 +1,7 @@
1
1
  import type {
2
- JWSHeaderParameters,
3
2
  JWTClaimVerificationOptions,
4
3
  JWTPayload,
4
+ JWTUnsecuredHeaderParameters,
5
5
  } from '../types';
6
6
 
7
7
  import { JWTInvalid } from '../utils/errors';
@@ -11,7 +11,7 @@ import { decodeBase64, encodeBase64 } from '../utils/base64';
11
11
 
12
12
  export interface UnsecuredResult {
13
13
  payload: JWTPayload;
14
- header: JWSHeaderParameters;
14
+ header: JWTUnsecuredHeaderParameters;
15
15
  }
16
16
 
17
17
  /**
@@ -86,18 +86,18 @@ export class UnsecuredJWT extends ProduceJWT {
86
86
  throw new JWTInvalid('Invalid Unsecured JWT');
87
87
  }
88
88
 
89
- let header: JWSHeaderParameters;
89
+ let header: JWTUnsecuredHeaderParameters;
90
90
  try {
91
91
  let decoded = decodeBase64(encodedHeader!);
92
92
  header = JSON.parse(decoded);
93
- if (header.alg !== 'none') throw new Error();
93
+ if ('alg' in header && header.alg !== 'none') throw new Error();
94
94
  } catch {
95
95
  throw new JWTInvalid('Invalid Unsecured JWT');
96
96
  }
97
97
 
98
98
  let payload = UnsecuredJWT.decodePayload(encodedPayload, options);
99
99
 
100
- return { payload, header };
100
+ return { payload, header: JSON.parse(decodeBase64(encodedHeader!)) };
101
101
  }
102
102
 
103
103
  /**
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import type { SupportedAlgorithm } from './algorithms';
2
3
 
3
4
  export interface JWTDecodeResult {
4
5
  /** JWT Claims Set. */
@@ -37,7 +38,7 @@ export interface JoseHeaderParameters {
37
38
  /** Recognized JWS Header Parameters, any other Header Members may also be present. */
38
39
  export interface JWSHeaderParameters extends JoseHeaderParameters {
39
40
  /** JWS "alg" (Algorithm) Header Parameter. */
40
- alg?: string;
41
+ alg?: SupportedAlgorithm;
41
42
 
42
43
  /**
43
44
  * This JWS Extension Header Parameter modifies the JWS Payload representation and the JWS Signing
@@ -52,9 +53,9 @@ export interface JWSHeaderParameters extends JoseHeaderParameters {
52
53
  [propName: string]: unknown;
53
54
  }
54
55
 
55
- /** Recognized Compact JWS Header Parameters, any other Header Members may also be present. */
56
- export interface CompactJWSHeaderParameters extends JWSHeaderParameters {
57
- alg: string;
56
+ /** JOSE Header for an unsecued JWT */
57
+ export interface JWTUnsecuredHeaderParameters extends JoseHeaderParameters {
58
+ alg?: 'none';
58
59
  }
59
60
 
60
61
  /** Recognized JWE Header Parameters, any other Header members may also be present. */
package/src/utils/asn1.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { getCoordinateOctetLength, getKtyFromAlg } from '../algorithms';
1
+ import {
2
+ getCoordinateOctetLength,
3
+ getKtyFromAlg,
4
+ type SupportedAlgorithm,
5
+ } from '../algorithms';
2
6
  import { IoReactNativeJwt } from './proxy';
3
7
  import { removePadding } from './base64';
4
8
  import { JOSENotSupported } from './errors';
@@ -20,7 +24,7 @@ import { JOSENotSupported } from './errors';
20
24
  */
21
25
  export const derToJose = async (
22
26
  asn1Signature: string,
23
- alg: string
27
+ alg: SupportedAlgorithm
24
28
  ): Promise<string> => {
25
29
  const kty = getKtyFromAlg(alg);
26
30
  if (kty === 'EC') {
@@ -0,0 +1,25 @@
1
+ import type { JWK } from 'src/types';
2
+
3
+ /**
4
+ * The interface of a cryptographic context to be used to sign tokens.
5
+ * Implementations are assumed to be initialized on a single, already-existing key pair.
6
+ *
7
+ * The generation and persistence of the key pair is delegated to the implementation.
8
+ */
9
+ export interface CryptoContext {
10
+ /**
11
+ * Retrieves the public key to be used in this context.
12
+ * MUST be the same key at every invocation.
13
+ * @returns The public key to be used
14
+ * @throws If no keys are found
15
+ */
16
+ getPublicKey: () => Promise<JWK>;
17
+ /**
18
+ * Produce a cryptographic signature for a given value.
19
+ * The signature MUST be produced using the private key paired with the public retrieved by getPublicKey()
20
+ * @param value The value to be signed
21
+ * @returns The signature
22
+ * @throws If no keys are found
23
+ */
24
+ getSignature: (value: string) => Promise<string>;
25
+ }