@pagopa/io-react-native-jwt 0.6.4 → 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.
- package/README.md +32 -36
- package/lib/commonjs/algorithms.js +34 -2
- package/lib/commonjs/algorithms.js.map +1 -1
- package/lib/commonjs/jwt/produce.js +8 -2
- package/lib/commonjs/jwt/produce.js.map +1 -1
- package/lib/commonjs/jwt/sign.js +54 -75
- package/lib/commonjs/jwt/sign.js.map +1 -1
- package/lib/commonjs/jwt/unsecured.js +2 -2
- package/lib/commonjs/jwt/unsecured.js.map +1 -1
- package/lib/commonjs/types.js +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils/asn1.js.map +1 -1
- package/lib/commonjs/utils/crypto.js +6 -0
- package/lib/commonjs/utils/crypto.js.map +1 -0
- package/lib/module/algorithms.js +31 -0
- package/lib/module/algorithms.js.map +1 -1
- package/lib/module/jwt/produce.js +8 -2
- package/lib/module/jwt/produce.js.map +1 -1
- package/lib/module/jwt/sign.js +56 -78
- package/lib/module/jwt/sign.js.map +1 -1
- package/lib/module/jwt/unsecured.js +2 -2
- package/lib/module/jwt/unsecured.js.map +1 -1
- package/lib/module/types.js +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/asn1.js.map +1 -1
- package/lib/module/utils/crypto.js +2 -0
- package/lib/module/utils/crypto.js.map +1 -0
- package/lib/typescript/algorithms.d.ts +5 -2
- package/lib/typescript/algorithms.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/jwt/produce.d.ts +1 -1
- package/lib/typescript/jwt/produce.d.ts.map +1 -1
- package/lib/typescript/jwt/sign.d.ts +26 -26
- package/lib/typescript/jwt/sign.d.ts.map +1 -1
- package/lib/typescript/jwt/unsecured.d.ts +2 -2
- package/lib/typescript/jwt/unsecured.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +5 -4
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/utils/asn1.d.ts +2 -1
- package/lib/typescript/utils/asn1.d.ts.map +1 -1
- package/lib/typescript/utils/crypto.d.ts +25 -0
- package/lib/typescript/utils/crypto.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/algorithms.ts +38 -3
- package/src/index.ts +1 -0
- package/src/jwt/produce.ts +4 -3
- package/src/jwt/sign.ts +64 -101
- package/src/jwt/unsecured.ts +5 -5
- package/src/types.ts +5 -4
- package/src/utils/asn1.ts +6 -2
- package/src/utils/crypto.ts +25 -0
package/src/jwt/produce.ts
CHANGED
|
@@ -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
|
|
7
|
+
protected _payload: JWTPayload = {};
|
|
8
8
|
|
|
9
9
|
/** @param payload The JWT Claims Set object. */
|
|
10
|
-
|
|
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,
|
|
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 {
|
|
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
|
|
20
|
-
* const signature = 'Axy0....'
|
|
20
|
+
* const crypto: Crypto = { any implementation fo Crypto interface }
|
|
21
21
|
*
|
|
22
|
-
* const jwt = new SignJWT(
|
|
23
|
-
* .
|
|
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
|
-
*
|
|
30
|
+
* jwt.data()
|
|
31
|
+
* await jwt.sign()
|
|
31
32
|
* ```
|
|
32
33
|
*/
|
|
34
|
+
|
|
33
35
|
export class SignJWT extends ProduceJWT {
|
|
34
|
-
private _protectedHeader
|
|
36
|
+
private _protectedHeader: Omit<JWSHeaderParameters, 'alg'> = {};
|
|
37
|
+
private crypto: CryptoContext;
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
|
-
*
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
*
|
|
83
|
+
* Return a signed JWT.
|
|
103
84
|
*
|
|
104
|
-
* @param jwtWithoutSignature JWT to sign that needs to be decoded.
|
|
105
85
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
92
|
+
const encodedJws = await (kty === 'EC'
|
|
93
|
+
? derToJose(signature, alg)
|
|
94
|
+
: removePadding(signature));
|
|
133
95
|
|
|
134
|
-
|
|
135
|
-
|
|
96
|
+
return `${unsigned}.${encodedJws}`;
|
|
97
|
+
}
|
|
136
98
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
107
|
+
* Decodes a signed JWT
|
|
145
108
|
*
|
|
146
|
-
* @param
|
|
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:
|
|
122
|
+
let protectedHeader: JWSHeaderParameters;
|
|
160
123
|
try {
|
|
161
124
|
let decoded = decodeBase64(encodedHeader!);
|
|
162
125
|
protectedHeader = JSON.parse(decoded);
|
package/src/jwt/unsecured.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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?:
|
|
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
|
-
/**
|
|
56
|
-
export interface
|
|
57
|
-
alg
|
|
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 {
|
|
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:
|
|
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
|
+
}
|