@logto/connector-apple 1.0.1 → 1.0.3
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/LICENSE +0 -6
- package/README.md +1 -71
- package/lib/index.js +187 -480
- package/package.json +4 -4
package/LICENSE
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
Portions of this software are licensed as follows:
|
|
2
|
-
|
|
3
|
-
* All content that resides under the "packages/cloud" directory of this repository, if that directory exists, is licensed under the license defined in "packages/cloud/LICENSE" (Elastic-2.0).
|
|
4
|
-
* All third party components incorporated into this software are licensed under the original license provided by the owner of the applicable component.
|
|
5
|
-
* Content outside of the above mentioned directories or restrictions above is available under the "MPL-2.0" license as defined below.
|
|
6
|
-
|
|
7
1
|
Mozilla Public License Version 2.0
|
|
8
2
|
==================================
|
|
9
3
|
|
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
The official Logto connector for Apple social sign-in.
|
|
4
4
|
|
|
5
|
-
Apple 社交登录 Logto 官方连接器 [中文文档](#apple-连接器)
|
|
6
|
-
|
|
7
5
|
**Table of contents**
|
|
8
6
|
|
|
9
7
|
- [Apple connector](#apple-connector)
|
|
@@ -12,12 +10,6 @@ Apple 社交登录 Logto 官方连接器 [中文文档](#apple-连接器)
|
|
|
12
10
|
- [Create an identifier](#create-an-identifier)
|
|
13
11
|
- [Enable Sign in with Apple for your identifier](#enable-sign-in-with-apple-for-your-identifier)
|
|
14
12
|
- [Test Apple connector](#test-apple-connector)
|
|
15
|
-
- [Apple 连接器](#apple-连接器)
|
|
16
|
-
- [开始上手](#开始上手)
|
|
17
|
-
- [为你的应用启用「通过 Apple 登录」](#为你的应用启用通过-apple-登录)
|
|
18
|
-
- [创建一个 identifier](#创建一个-identifier)
|
|
19
|
-
- [为你的 identifier 启用「通过 Apple 登录」](#为你的-identifier-启用通过-apple-登录)
|
|
20
|
-
- [测试 Apple 连接器](#测试-apple-连接器)
|
|
21
13
|
|
|
22
14
|
## Get started
|
|
23
15
|
|
|
@@ -77,66 +69,4 @@ Click "Next" then "Done" to close the modal. Click "Continue" on the top-right c
|
|
|
77
69
|
|
|
78
70
|
## Test Apple connector
|
|
79
71
|
|
|
80
|
-
That's it. The Apple connector should be available in both web and native apps. Don't forget to [Enable connector in sign-in experience](https://docs.logto.io/docs/tutorials/get-started/
|
|
81
|
-
|
|
82
|
-
# Apple 连接器
|
|
83
|
-
|
|
84
|
-
## 开始上手
|
|
85
|
-
|
|
86
|
-
如果你还不知道连接器的概念,或者还不知道如何将本连接器添加至你的「登录体验」,请先参见 [Logto 教程](https://docs.logto.io/zh-cn/docs/tutorials/get-started/enable-social-sign-in)。
|
|
87
|
-
|
|
88
|
-
> ℹ️ **Note**
|
|
89
|
-
>
|
|
90
|
-
> 如果你的应用有其他的社交登录方式,AppStore 要求必须同时有 Apple 登录。
|
|
91
|
-
> 如果同时提供 Android 应用,在 Android 设备上同时提供 Apple 登录会让用户体验很棒。
|
|
92
|
-
|
|
93
|
-
在继续前,你需要加入 [Apple Developer Program](https://developer.apple.com/programs/)。
|
|
94
|
-
|
|
95
|
-
### 为你的应用启用「通过 Apple 登录」
|
|
96
|
-
|
|
97
|
-
> ⚠️ **Caution**
|
|
98
|
-
>
|
|
99
|
-
> 即使你只想在 web 应用中实现「通过 Apple 登录」,你仍需要拥有一个拥抱 AppStore 生态的应用(即:有一个有效的 App ID)。
|
|
100
|
-
|
|
101
|
-
你可以通过 Xcode -> Project settings -> Signing & Capabilities 来启用,或者访问 [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list/bundleId)。
|
|
102
|
-
|
|
103
|
-

|
|
104
|
-
|
|
105
|
-
参见 [Apple 官方文档](https://developer.apple.com/documentation/sign_in_with_apple/configuring_your_environment_for_sign_in_with_apple) 里的「Enable an App ID」章节以了解更多。
|
|
106
|
-
|
|
107
|
-
### 创建一个 identifier
|
|
108
|
-
|
|
109
|
-
1. 访问 [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list/serviceId),并点按在「Identifier」旁边的「+」按钮。
|
|
110
|
-
2. 在「Register a new identifier」页面,选择「Services IDs」并点按「Continue」。
|
|
111
|
-
3. 填写「Description」与「Identifier」(例如 `Logto Test` 和 `io.logto.test`),并点按「Continue」。
|
|
112
|
-
4. 再次检查相关信息并点按「Register」。
|
|
113
|
-
|
|
114
|
-
### 为你的 identifier 启用「通过 Apple 登录」
|
|
115
|
-
|
|
116
|
-
点按你刚刚创建的 identifier。在详情页勾选「Sign in with Apple」并点按「Configure」。
|
|
117
|
-
|
|
118
|
-

|
|
119
|
-
|
|
120
|
-
在打开的对话框中,选择刚刚启用了「通过 Apple 登录」的 App ID。
|
|
121
|
-
|
|
122
|
-
输入你的 Logto 实例域名(不含协议和端口),例如 `your.logto.domain`;并输入「Return URL」(即 Redirect URI)。Return URL 的值是 Logto URL 加上 `/callback/${connector_id}`,例如 `https://your.logto.domain/callback/apple-universal`。在管理控制台创建了 Apple 连接器之后,在详情页可以找到生成的随机 `connector_id`。
|
|
123
|
-
|
|
124
|
-

|
|
125
|
-
|
|
126
|
-
点按「Next」以及「Done」以关闭对话框。点按右上角的「Continue」,接着点按「Save」以保存你的配置。
|
|
127
|
-
|
|
128
|
-
> ⚠️ **Caution**
|
|
129
|
-
>
|
|
130
|
-
> Apple _不允许_ HTTP 协议或 `localhost` 域名作为 Return URL。
|
|
131
|
-
>
|
|
132
|
-
> 如果你想在本地进行测试,你需要编辑 `/etc/hosts` 文件以映射 localhost 到一个自定义域名,并设置一个本地的 HTTPS 环境。[mkcert](https://github.com/FiloSottile/mkcert) 可以帮助你设置本地 HTTPS。
|
|
133
|
-
|
|
134
|
-
> ℹ️ **Note**
|
|
135
|
-
>
|
|
136
|
-
> 本连接器暂时不支持自定义 `scope`(例如 name,email)。因为在 `scope` 非空时,Apple 要求 `response_mode` 为 `form_post`,与现在连接器的设计不兼容。
|
|
137
|
-
>
|
|
138
|
-
> 我们将稍后解决这个问题。
|
|
139
|
-
|
|
140
|
-
## 测试 Apple 连接器
|
|
141
|
-
|
|
142
|
-
大功告成。Apple 连接器应该在 web 和原生应用中都可用了。别忘了 [在登录体验中启用本连接器](https://docs.logto.io/zh-cn/docs/tutorials/get-started/enable-social-sign-in/#%E5%9C%A8%E7%99%BB%E5%BD%95%E4%BD%93%E9%AA%8C%E4%B8%AD%E5%90%AF%E7%94%A8%E8%BF%9E%E6%8E%A5%E5%99%A8)。
|
|
72
|
+
That's it. The Apple connector should be available in both web and native apps. Don't forget to [Enable connector in sign-in experience](https://docs.logto.io/docs/tutorials/get-started/passwordless-sign-in-by-adding-connectors#enable-social-sign-in).
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ConnectorPlatform, ConnectorConfigFormItemType, ConnectorType, validateConfig, ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
|
|
2
|
-
import * as crypto from 'crypto';
|
|
3
|
-
import {
|
|
2
|
+
import * as crypto from 'node:crypto';
|
|
3
|
+
import crypto__default, { KeyObject, createPrivateKey, createPublicKey, constants, createSecretKey } from 'node:crypto';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import
|
|
6
|
-
import * as
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import * as
|
|
10
|
-
import {
|
|
5
|
+
import * as http from 'node:http';
|
|
6
|
+
import * as https from 'node:https';
|
|
7
|
+
import { once } from 'node:events';
|
|
8
|
+
import { Buffer as Buffer$1 } from 'node:buffer';
|
|
9
|
+
import * as util from 'node:util';
|
|
10
|
+
import { promisify } from 'node:util';
|
|
11
11
|
|
|
12
12
|
// https://github.com/facebook/jest/issues/7547
|
|
13
13
|
const assert = (value, error) => {
|
|
@@ -20,22 +20,22 @@ const assert = (value, error) => {
|
|
|
20
20
|
|
|
21
21
|
const POOL_SIZE_MULTIPLIER = 128;
|
|
22
22
|
let pool, poolOffset;
|
|
23
|
-
|
|
23
|
+
function fillPool(bytes) {
|
|
24
24
|
if (!pool || pool.length < bytes) {
|
|
25
25
|
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
26
|
-
|
|
26
|
+
crypto__default.getRandomValues(pool);
|
|
27
27
|
poolOffset = 0;
|
|
28
28
|
} else if (poolOffset + bytes > pool.length) {
|
|
29
|
-
|
|
29
|
+
crypto__default.getRandomValues(pool);
|
|
30
30
|
poolOffset = 0;
|
|
31
31
|
}
|
|
32
32
|
poolOffset += bytes;
|
|
33
|
-
}
|
|
34
|
-
|
|
33
|
+
}
|
|
34
|
+
function random(bytes) {
|
|
35
35
|
fillPool((bytes -= 0));
|
|
36
36
|
return pool.subarray(poolOffset - bytes, poolOffset)
|
|
37
|
-
}
|
|
38
|
-
|
|
37
|
+
}
|
|
38
|
+
function customRandom(alphabet, defaultSize, getRandom) {
|
|
39
39
|
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1;
|
|
40
40
|
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length);
|
|
41
41
|
return (size = defaultSize) => {
|
|
@@ -49,14 +49,33 @@ let customRandom = (alphabet, defaultSize, getRandom) => {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
customRandom(alphabet, size, random)
|
|
52
|
+
}
|
|
53
|
+
function customAlphabet(alphabet, size = 21) {
|
|
54
|
+
return customRandom(alphabet, size, random)
|
|
55
|
+
}
|
|
55
56
|
|
|
56
57
|
const lowercaseAlphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
57
58
|
const alphabet = `${lowercaseAlphabet}ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|
58
|
-
const buildIdGenerator = (size, includingUppercase =
|
|
59
|
-
|
|
59
|
+
const buildIdGenerator = (size, includingUppercase = true) => customAlphabet(includingUppercase ? alphabet : lowercaseAlphabet, size);
|
|
60
|
+
/**
|
|
61
|
+
* Generate a standard id with 21 characters, including lowercase letters and numbers.
|
|
62
|
+
*
|
|
63
|
+
* @see {@link lowercaseAlphabet}
|
|
64
|
+
*/
|
|
65
|
+
const generateStandardId = buildIdGenerator(21, false);
|
|
66
|
+
/**
|
|
67
|
+
* Generate a standard short id with 12 characters, including lowercase letters and numbers.
|
|
68
|
+
*
|
|
69
|
+
* @see {@link lowercaseAlphabet}
|
|
70
|
+
*/
|
|
71
|
+
buildIdGenerator(12, false);
|
|
72
|
+
/**
|
|
73
|
+
* Generate a standard secret with 32 characters, including uppercase letters, lowercase
|
|
74
|
+
* letters, and numbers.
|
|
75
|
+
*
|
|
76
|
+
* @see {@link alphabet}
|
|
77
|
+
*/
|
|
78
|
+
buildIdGenerator(32);
|
|
60
79
|
|
|
61
80
|
const encoder = new TextEncoder();
|
|
62
81
|
const decoder = new TextDecoder();
|
|
@@ -78,141 +97,109 @@ function normalize(input) {
|
|
|
78
97
|
}
|
|
79
98
|
return encoded;
|
|
80
99
|
}
|
|
81
|
-
|
|
82
|
-
const decode = (input) => Buffer$1.from(normalize(input), 'base64');
|
|
100
|
+
const decode = (input) => new Uint8Array(Buffer$1.from(normalize(input), 'base64'));
|
|
83
101
|
|
|
84
102
|
class JOSEError extends Error {
|
|
103
|
+
static get code() {
|
|
104
|
+
return 'ERR_JOSE_GENERIC';
|
|
105
|
+
}
|
|
106
|
+
code = 'ERR_JOSE_GENERIC';
|
|
85
107
|
constructor(message) {
|
|
86
|
-
var _a;
|
|
87
108
|
super(message);
|
|
88
|
-
this.code = 'ERR_JOSE_GENERIC';
|
|
89
109
|
this.name = this.constructor.name;
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
static get code() {
|
|
93
|
-
return 'ERR_JOSE_GENERIC';
|
|
110
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
94
111
|
}
|
|
95
112
|
}
|
|
96
113
|
class JWTClaimValidationFailed extends JOSEError {
|
|
114
|
+
static get code() {
|
|
115
|
+
return 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
|
116
|
+
}
|
|
117
|
+
code = 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
|
118
|
+
claim;
|
|
119
|
+
reason;
|
|
97
120
|
constructor(message, claim = 'unspecified', reason = 'unspecified') {
|
|
98
121
|
super(message);
|
|
99
|
-
this.code = 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
|
100
122
|
this.claim = claim;
|
|
101
123
|
this.reason = reason;
|
|
102
124
|
}
|
|
103
|
-
static get code() {
|
|
104
|
-
return 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
|
105
|
-
}
|
|
106
125
|
}
|
|
107
126
|
class JWTExpired extends JOSEError {
|
|
127
|
+
static get code() {
|
|
128
|
+
return 'ERR_JWT_EXPIRED';
|
|
129
|
+
}
|
|
130
|
+
code = 'ERR_JWT_EXPIRED';
|
|
131
|
+
claim;
|
|
132
|
+
reason;
|
|
108
133
|
constructor(message, claim = 'unspecified', reason = 'unspecified') {
|
|
109
134
|
super(message);
|
|
110
|
-
this.code = 'ERR_JWT_EXPIRED';
|
|
111
135
|
this.claim = claim;
|
|
112
136
|
this.reason = reason;
|
|
113
137
|
}
|
|
114
|
-
static get code() {
|
|
115
|
-
return 'ERR_JWT_EXPIRED';
|
|
116
|
-
}
|
|
117
138
|
}
|
|
118
139
|
class JOSEAlgNotAllowed extends JOSEError {
|
|
119
|
-
constructor() {
|
|
120
|
-
super(...arguments);
|
|
121
|
-
this.code = 'ERR_JOSE_ALG_NOT_ALLOWED';
|
|
122
|
-
}
|
|
123
140
|
static get code() {
|
|
124
141
|
return 'ERR_JOSE_ALG_NOT_ALLOWED';
|
|
125
142
|
}
|
|
143
|
+
code = 'ERR_JOSE_ALG_NOT_ALLOWED';
|
|
126
144
|
}
|
|
127
145
|
class JOSENotSupported extends JOSEError {
|
|
128
|
-
constructor() {
|
|
129
|
-
super(...arguments);
|
|
130
|
-
this.code = 'ERR_JOSE_NOT_SUPPORTED';
|
|
131
|
-
}
|
|
132
146
|
static get code() {
|
|
133
147
|
return 'ERR_JOSE_NOT_SUPPORTED';
|
|
134
148
|
}
|
|
149
|
+
code = 'ERR_JOSE_NOT_SUPPORTED';
|
|
135
150
|
}
|
|
136
151
|
class JWSInvalid extends JOSEError {
|
|
137
|
-
constructor() {
|
|
138
|
-
super(...arguments);
|
|
139
|
-
this.code = 'ERR_JWS_INVALID';
|
|
140
|
-
}
|
|
141
152
|
static get code() {
|
|
142
153
|
return 'ERR_JWS_INVALID';
|
|
143
154
|
}
|
|
155
|
+
code = 'ERR_JWS_INVALID';
|
|
144
156
|
}
|
|
145
157
|
class JWTInvalid extends JOSEError {
|
|
146
|
-
constructor() {
|
|
147
|
-
super(...arguments);
|
|
148
|
-
this.code = 'ERR_JWT_INVALID';
|
|
149
|
-
}
|
|
150
158
|
static get code() {
|
|
151
159
|
return 'ERR_JWT_INVALID';
|
|
152
160
|
}
|
|
161
|
+
code = 'ERR_JWT_INVALID';
|
|
153
162
|
}
|
|
154
163
|
class JWKSInvalid extends JOSEError {
|
|
155
|
-
constructor() {
|
|
156
|
-
super(...arguments);
|
|
157
|
-
this.code = 'ERR_JWKS_INVALID';
|
|
158
|
-
}
|
|
159
164
|
static get code() {
|
|
160
165
|
return 'ERR_JWKS_INVALID';
|
|
161
166
|
}
|
|
167
|
+
code = 'ERR_JWKS_INVALID';
|
|
162
168
|
}
|
|
163
169
|
class JWKSNoMatchingKey extends JOSEError {
|
|
164
|
-
constructor() {
|
|
165
|
-
super(...arguments);
|
|
166
|
-
this.code = 'ERR_JWKS_NO_MATCHING_KEY';
|
|
167
|
-
this.message = 'no applicable key found in the JSON Web Key Set';
|
|
168
|
-
}
|
|
169
170
|
static get code() {
|
|
170
171
|
return 'ERR_JWKS_NO_MATCHING_KEY';
|
|
171
172
|
}
|
|
173
|
+
code = 'ERR_JWKS_NO_MATCHING_KEY';
|
|
174
|
+
message = 'no applicable key found in the JSON Web Key Set';
|
|
172
175
|
}
|
|
173
176
|
class JWKSMultipleMatchingKeys extends JOSEError {
|
|
174
|
-
|
|
175
|
-
super(...arguments);
|
|
176
|
-
this.code = 'ERR_JWKS_MULTIPLE_MATCHING_KEYS';
|
|
177
|
-
this.message = 'multiple matching keys found in the JSON Web Key Set';
|
|
178
|
-
}
|
|
177
|
+
[Symbol.asyncIterator];
|
|
179
178
|
static get code() {
|
|
180
179
|
return 'ERR_JWKS_MULTIPLE_MATCHING_KEYS';
|
|
181
180
|
}
|
|
181
|
+
code = 'ERR_JWKS_MULTIPLE_MATCHING_KEYS';
|
|
182
|
+
message = 'multiple matching keys found in the JSON Web Key Set';
|
|
182
183
|
}
|
|
183
184
|
class JWKSTimeout extends JOSEError {
|
|
184
|
-
constructor() {
|
|
185
|
-
super(...arguments);
|
|
186
|
-
this.code = 'ERR_JWKS_TIMEOUT';
|
|
187
|
-
this.message = 'request timed out';
|
|
188
|
-
}
|
|
189
185
|
static get code() {
|
|
190
186
|
return 'ERR_JWKS_TIMEOUT';
|
|
191
187
|
}
|
|
188
|
+
code = 'ERR_JWKS_TIMEOUT';
|
|
189
|
+
message = 'request timed out';
|
|
192
190
|
}
|
|
193
191
|
class JWSSignatureVerificationFailed extends JOSEError {
|
|
194
|
-
constructor() {
|
|
195
|
-
super(...arguments);
|
|
196
|
-
this.code = 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED';
|
|
197
|
-
this.message = 'signature verification failed';
|
|
198
|
-
}
|
|
199
192
|
static get code() {
|
|
200
193
|
return 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED';
|
|
201
194
|
}
|
|
195
|
+
code = 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED';
|
|
196
|
+
message = 'signature verification failed';
|
|
202
197
|
}
|
|
203
198
|
|
|
204
|
-
var isKeyObject = util.types.isKeyObject
|
|
205
|
-
? (obj) => util.types.isKeyObject(obj)
|
|
206
|
-
: (obj) => obj != null && obj instanceof KeyObject;
|
|
199
|
+
var isKeyObject = (obj) => util.types.isKeyObject(obj);
|
|
207
200
|
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
:
|
|
211
|
-
(key) => false;
|
|
212
|
-
|
|
213
|
-
function isCloudflareWorkers() {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
201
|
+
const webcrypto = crypto.webcrypto;
|
|
202
|
+
const isCryptoKey = (key) => util.types.isCryptoKey(key);
|
|
216
203
|
|
|
217
204
|
function unusable(name, prop = 'algorithm.name') {
|
|
218
205
|
return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
@@ -286,11 +273,6 @@ function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
286
273
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
287
274
|
break;
|
|
288
275
|
}
|
|
289
|
-
case isCloudflareWorkers() : {
|
|
290
|
-
if (!isAlgorithm(key.algorithm, 'NODE-ED25519'))
|
|
291
|
-
throw unusable('NODE-ED25519');
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
276
|
case 'EdDSA': {
|
|
295
277
|
if (key.algorithm.name !== 'Ed25519' && key.algorithm.name !== 'Ed448') {
|
|
296
278
|
throw unusable('Ed25519 or Ed448');
|
|
@@ -347,7 +329,7 @@ function withAlg(alg, actual, ...types) {
|
|
|
347
329
|
|
|
348
330
|
var isKeyLike = (key) => isKeyObject(key) || isCryptoKey(key);
|
|
349
331
|
const types = ['KeyObject'];
|
|
350
|
-
if (
|
|
332
|
+
if (globalThis.CryptoKey || webcrypto?.CryptoKey) {
|
|
351
333
|
types.push('CryptoKey');
|
|
352
334
|
}
|
|
353
335
|
|
|
@@ -390,11 +372,6 @@ function isObject(input) {
|
|
|
390
372
|
return Object.getPrototypeOf(input) === proto;
|
|
391
373
|
}
|
|
392
374
|
|
|
393
|
-
const p256 = Buffer$1.from([42, 134, 72, 206, 61, 3, 1, 7]);
|
|
394
|
-
const p384 = Buffer$1.from([43, 129, 4, 0, 34]);
|
|
395
|
-
const p521 = Buffer$1.from([43, 129, 4, 0, 35]);
|
|
396
|
-
const secp256k1 = Buffer$1.from([43, 129, 4, 0, 10]);
|
|
397
|
-
const weakMap$1 = new WeakMap();
|
|
398
375
|
const namedCurveToJOSE = (namedCurve) => {
|
|
399
376
|
switch (namedCurve) {
|
|
400
377
|
case 'prime256v1':
|
|
@@ -410,7 +387,6 @@ const namedCurveToJOSE = (namedCurve) => {
|
|
|
410
387
|
}
|
|
411
388
|
};
|
|
412
389
|
const getNamedCurve = (kee, raw) => {
|
|
413
|
-
var _a;
|
|
414
390
|
let key;
|
|
415
391
|
if (isCryptoKey(kee)) {
|
|
416
392
|
key = KeyObject.from(kee);
|
|
@@ -432,315 +408,38 @@ const getNamedCurve = (kee, raw) => {
|
|
|
432
408
|
case 'x448':
|
|
433
409
|
return `X${key.asymmetricKeyType.slice(1)}`;
|
|
434
410
|
case 'ec': {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
let namedCurve = (_a = key.asymmetricKeyDetails) === null || _a === void 0 ? void 0 : _a.namedCurve;
|
|
439
|
-
if (!namedCurve && key.type === 'private') {
|
|
440
|
-
namedCurve = getNamedCurve(createPublicKey(key), true);
|
|
441
|
-
}
|
|
442
|
-
else if (!namedCurve) {
|
|
443
|
-
const buf = key.export({ format: 'der', type: 'spki' });
|
|
444
|
-
const i = buf[1] < 128 ? 14 : 15;
|
|
445
|
-
const len = buf[i];
|
|
446
|
-
const curveOid = buf.slice(i + 1, i + 1 + len);
|
|
447
|
-
if (curveOid.equals(p256)) {
|
|
448
|
-
namedCurve = 'prime256v1';
|
|
449
|
-
}
|
|
450
|
-
else if (curveOid.equals(p384)) {
|
|
451
|
-
namedCurve = 'secp384r1';
|
|
452
|
-
}
|
|
453
|
-
else if (curveOid.equals(p521)) {
|
|
454
|
-
namedCurve = 'secp521r1';
|
|
455
|
-
}
|
|
456
|
-
else if (curveOid.equals(secp256k1)) {
|
|
457
|
-
namedCurve = 'secp256k1';
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
throw new JOSENotSupported('Unsupported key curve for this operation');
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
if (raw)
|
|
411
|
+
let namedCurve = key.asymmetricKeyDetails.namedCurve;
|
|
412
|
+
if (raw) {
|
|
464
413
|
return namedCurve;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return curve;
|
|
414
|
+
}
|
|
415
|
+
return namedCurveToJOSE(namedCurve);
|
|
468
416
|
}
|
|
469
417
|
default:
|
|
470
418
|
throw new TypeError('Invalid asymmetric key type for this operation');
|
|
471
419
|
}
|
|
472
420
|
};
|
|
473
|
-
function setCurve(keyObject, curve) {
|
|
474
|
-
weakMap$1.set(keyObject, curve);
|
|
475
|
-
}
|
|
476
421
|
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
if ((len & 0x80) === 0) {
|
|
481
|
-
if (index === 0) {
|
|
482
|
-
return len;
|
|
483
|
-
}
|
|
484
|
-
return getLength(buf.subarray(2 + len), index - 1);
|
|
485
|
-
}
|
|
486
|
-
const num = len & 0x7f;
|
|
487
|
-
len = 0;
|
|
488
|
-
for (let i = 0; i < num; i++) {
|
|
489
|
-
len <<= 8;
|
|
490
|
-
const j = buf.readUInt8(2 + i);
|
|
491
|
-
len |= j;
|
|
492
|
-
}
|
|
493
|
-
if (index === 0) {
|
|
494
|
-
return len;
|
|
495
|
-
}
|
|
496
|
-
return getLength(buf.subarray(2 + len), index - 1);
|
|
497
|
-
};
|
|
498
|
-
const getLengthOfSeqIndex = (sequence, index) => {
|
|
499
|
-
const len = sequence.readUInt8(1);
|
|
500
|
-
if ((len & 0x80) === 0) {
|
|
501
|
-
return getLength(sequence.subarray(2), index);
|
|
502
|
-
}
|
|
503
|
-
const num = len & 0x7f;
|
|
504
|
-
return getLength(sequence.subarray(2 + num), index);
|
|
505
|
-
};
|
|
506
|
-
const getModulusLength = (key) => {
|
|
507
|
-
var _a, _b;
|
|
508
|
-
if (weakMap.has(key)) {
|
|
509
|
-
return weakMap.get(key);
|
|
510
|
-
}
|
|
511
|
-
const modulusLength = (_b = (_a = key.asymmetricKeyDetails) === null || _a === void 0 ? void 0 : _a.modulusLength) !== null && _b !== void 0 ? _b : (getLengthOfSeqIndex(key.export({ format: 'der', type: 'pkcs1' }), key.type === 'private' ? 1 : 0) -
|
|
512
|
-
1) <<
|
|
513
|
-
3;
|
|
514
|
-
weakMap.set(key, modulusLength);
|
|
515
|
-
return modulusLength;
|
|
516
|
-
};
|
|
517
|
-
const setModulusLength = (keyObject, modulusLength) => {
|
|
518
|
-
weakMap.set(keyObject, modulusLength);
|
|
519
|
-
};
|
|
520
|
-
var checkModulusLength = (key, alg) => {
|
|
521
|
-
if (getModulusLength(key) < 2048) {
|
|
422
|
+
var checkKeyLength = (key, alg) => {
|
|
423
|
+
const { modulusLength } = key.asymmetricKeyDetails;
|
|
424
|
+
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
|
|
522
425
|
throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
|
|
523
426
|
}
|
|
524
427
|
};
|
|
525
428
|
|
|
526
|
-
const tagInteger = 0x02;
|
|
527
|
-
const tagBitStr = 0x03;
|
|
528
|
-
const tagOctStr = 0x04;
|
|
529
|
-
const tagSequence = 0x30;
|
|
530
|
-
const bZero = Buffer$1.from([0x00]);
|
|
531
|
-
const bTagInteger = Buffer$1.from([tagInteger]);
|
|
532
|
-
const bTagBitStr = Buffer$1.from([tagBitStr]);
|
|
533
|
-
const bTagSequence = Buffer$1.from([tagSequence]);
|
|
534
|
-
const bTagOctStr = Buffer$1.from([tagOctStr]);
|
|
535
|
-
const encodeLength = (len) => {
|
|
536
|
-
if (len < 128)
|
|
537
|
-
return Buffer$1.from([len]);
|
|
538
|
-
const buffer = Buffer$1.alloc(5);
|
|
539
|
-
buffer.writeUInt32BE(len, 1);
|
|
540
|
-
let offset = 1;
|
|
541
|
-
while (buffer[offset] === 0)
|
|
542
|
-
offset++;
|
|
543
|
-
buffer[offset - 1] = 0x80 | (5 - offset);
|
|
544
|
-
return buffer.slice(offset - 1);
|
|
545
|
-
};
|
|
546
|
-
const oids = new Map([
|
|
547
|
-
['P-256', Buffer$1.from('06 08 2A 86 48 CE 3D 03 01 07'.replace(/ /g, ''), 'hex')],
|
|
548
|
-
['secp256k1', Buffer$1.from('06 05 2B 81 04 00 0A'.replace(/ /g, ''), 'hex')],
|
|
549
|
-
['P-384', Buffer$1.from('06 05 2B 81 04 00 22'.replace(/ /g, ''), 'hex')],
|
|
550
|
-
['P-521', Buffer$1.from('06 05 2B 81 04 00 23'.replace(/ /g, ''), 'hex')],
|
|
551
|
-
['ecPublicKey', Buffer$1.from('06 07 2A 86 48 CE 3D 02 01'.replace(/ /g, ''), 'hex')],
|
|
552
|
-
['X25519', Buffer$1.from('06 03 2B 65 6E'.replace(/ /g, ''), 'hex')],
|
|
553
|
-
['X448', Buffer$1.from('06 03 2B 65 6F'.replace(/ /g, ''), 'hex')],
|
|
554
|
-
['Ed25519', Buffer$1.from('06 03 2B 65 70'.replace(/ /g, ''), 'hex')],
|
|
555
|
-
['Ed448', Buffer$1.from('06 03 2B 65 71'.replace(/ /g, ''), 'hex')],
|
|
556
|
-
]);
|
|
557
|
-
class DumbAsn1Encoder {
|
|
558
|
-
constructor() {
|
|
559
|
-
this.length = 0;
|
|
560
|
-
this.elements = [];
|
|
561
|
-
}
|
|
562
|
-
oidFor(oid) {
|
|
563
|
-
const bOid = oids.get(oid);
|
|
564
|
-
if (!bOid) {
|
|
565
|
-
throw new JOSENotSupported('Invalid or unsupported OID');
|
|
566
|
-
}
|
|
567
|
-
this.elements.push(bOid);
|
|
568
|
-
this.length += bOid.length;
|
|
569
|
-
}
|
|
570
|
-
zero() {
|
|
571
|
-
this.elements.push(bTagInteger, Buffer$1.from([0x01]), bZero);
|
|
572
|
-
this.length += 3;
|
|
573
|
-
}
|
|
574
|
-
one() {
|
|
575
|
-
this.elements.push(bTagInteger, Buffer$1.from([0x01]), Buffer$1.from([0x01]));
|
|
576
|
-
this.length += 3;
|
|
577
|
-
}
|
|
578
|
-
unsignedInteger(integer) {
|
|
579
|
-
if (integer[0] & 0x80) {
|
|
580
|
-
const len = encodeLength(integer.length + 1);
|
|
581
|
-
this.elements.push(bTagInteger, len, bZero, integer);
|
|
582
|
-
this.length += 2 + len.length + integer.length;
|
|
583
|
-
}
|
|
584
|
-
else {
|
|
585
|
-
let i = 0;
|
|
586
|
-
while (integer[i] === 0 && (integer[i + 1] & 0x80) === 0)
|
|
587
|
-
i++;
|
|
588
|
-
const len = encodeLength(integer.length - i);
|
|
589
|
-
this.elements.push(bTagInteger, encodeLength(integer.length - i), integer.slice(i));
|
|
590
|
-
this.length += 1 + len.length + integer.length - i;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
octStr(octStr) {
|
|
594
|
-
const len = encodeLength(octStr.length);
|
|
595
|
-
this.elements.push(bTagOctStr, encodeLength(octStr.length), octStr);
|
|
596
|
-
this.length += 1 + len.length + octStr.length;
|
|
597
|
-
}
|
|
598
|
-
bitStr(bitS) {
|
|
599
|
-
const len = encodeLength(bitS.length + 1);
|
|
600
|
-
this.elements.push(bTagBitStr, encodeLength(bitS.length + 1), bZero, bitS);
|
|
601
|
-
this.length += 1 + len.length + bitS.length + 1;
|
|
602
|
-
}
|
|
603
|
-
add(seq) {
|
|
604
|
-
this.elements.push(seq);
|
|
605
|
-
this.length += seq.length;
|
|
606
|
-
}
|
|
607
|
-
end(tag = bTagSequence) {
|
|
608
|
-
const len = encodeLength(this.length);
|
|
609
|
-
return Buffer$1.concat([tag, len, ...this.elements], 1 + len.length + this.length);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const [major$2, minor$2] = process.version
|
|
614
|
-
.slice(1)
|
|
615
|
-
.split('.')
|
|
616
|
-
.map((str) => parseInt(str, 10));
|
|
617
|
-
const jwkImportSupported = major$2 >= 16 || (major$2 === 15 && minor$2 >= 12);
|
|
618
429
|
const parse = (jwk) => {
|
|
619
|
-
|
|
620
|
-
return jwk.d
|
|
621
|
-
? createPrivateKey({ format: 'jwk', key: jwk })
|
|
622
|
-
: createPublicKey({ format: 'jwk', key: jwk });
|
|
623
|
-
}
|
|
624
|
-
switch (jwk.kty) {
|
|
625
|
-
case 'oct': {
|
|
626
|
-
return createSecretKey(decode(jwk.k));
|
|
627
|
-
}
|
|
628
|
-
case 'RSA': {
|
|
629
|
-
const enc = new DumbAsn1Encoder();
|
|
630
|
-
const isPrivate = jwk.d !== undefined;
|
|
631
|
-
const modulus = Buffer$1.from(jwk.n, 'base64');
|
|
632
|
-
const exponent = Buffer$1.from(jwk.e, 'base64');
|
|
633
|
-
if (isPrivate) {
|
|
634
|
-
enc.zero();
|
|
635
|
-
enc.unsignedInteger(modulus);
|
|
636
|
-
enc.unsignedInteger(exponent);
|
|
637
|
-
enc.unsignedInteger(Buffer$1.from(jwk.d, 'base64'));
|
|
638
|
-
enc.unsignedInteger(Buffer$1.from(jwk.p, 'base64'));
|
|
639
|
-
enc.unsignedInteger(Buffer$1.from(jwk.q, 'base64'));
|
|
640
|
-
enc.unsignedInteger(Buffer$1.from(jwk.dp, 'base64'));
|
|
641
|
-
enc.unsignedInteger(Buffer$1.from(jwk.dq, 'base64'));
|
|
642
|
-
enc.unsignedInteger(Buffer$1.from(jwk.qi, 'base64'));
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
enc.unsignedInteger(modulus);
|
|
646
|
-
enc.unsignedInteger(exponent);
|
|
647
|
-
}
|
|
648
|
-
const der = enc.end();
|
|
649
|
-
const createInput = {
|
|
650
|
-
key: der,
|
|
651
|
-
format: 'der',
|
|
652
|
-
type: 'pkcs1',
|
|
653
|
-
};
|
|
654
|
-
const keyObject = isPrivate ? createPrivateKey(createInput) : createPublicKey(createInput);
|
|
655
|
-
setModulusLength(keyObject, modulus.length << 3);
|
|
656
|
-
return keyObject;
|
|
657
|
-
}
|
|
658
|
-
case 'EC': {
|
|
659
|
-
const enc = new DumbAsn1Encoder();
|
|
660
|
-
const isPrivate = jwk.d !== undefined;
|
|
661
|
-
const pub = Buffer$1.concat([
|
|
662
|
-
Buffer$1.alloc(1, 4),
|
|
663
|
-
Buffer$1.from(jwk.x, 'base64'),
|
|
664
|
-
Buffer$1.from(jwk.y, 'base64'),
|
|
665
|
-
]);
|
|
666
|
-
if (isPrivate) {
|
|
667
|
-
enc.zero();
|
|
668
|
-
const enc$1 = new DumbAsn1Encoder();
|
|
669
|
-
enc$1.oidFor('ecPublicKey');
|
|
670
|
-
enc$1.oidFor(jwk.crv);
|
|
671
|
-
enc.add(enc$1.end());
|
|
672
|
-
const enc$2 = new DumbAsn1Encoder();
|
|
673
|
-
enc$2.one();
|
|
674
|
-
enc$2.octStr(Buffer$1.from(jwk.d, 'base64'));
|
|
675
|
-
const enc$3 = new DumbAsn1Encoder();
|
|
676
|
-
enc$3.bitStr(pub);
|
|
677
|
-
const f2 = enc$3.end(Buffer$1.from([0xa1]));
|
|
678
|
-
enc$2.add(f2);
|
|
679
|
-
const f = enc$2.end();
|
|
680
|
-
const enc$4 = new DumbAsn1Encoder();
|
|
681
|
-
enc$4.add(f);
|
|
682
|
-
const f3 = enc$4.end(Buffer$1.from([0x04]));
|
|
683
|
-
enc.add(f3);
|
|
684
|
-
const der = enc.end();
|
|
685
|
-
const keyObject = createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
|
|
686
|
-
setCurve(keyObject, jwk.crv);
|
|
687
|
-
return keyObject;
|
|
688
|
-
}
|
|
689
|
-
const enc$1 = new DumbAsn1Encoder();
|
|
690
|
-
enc$1.oidFor('ecPublicKey');
|
|
691
|
-
enc$1.oidFor(jwk.crv);
|
|
692
|
-
enc.add(enc$1.end());
|
|
693
|
-
enc.bitStr(pub);
|
|
694
|
-
const der = enc.end();
|
|
695
|
-
const keyObject = createPublicKey({ key: der, format: 'der', type: 'spki' });
|
|
696
|
-
setCurve(keyObject, jwk.crv);
|
|
697
|
-
return keyObject;
|
|
698
|
-
}
|
|
699
|
-
case 'OKP': {
|
|
700
|
-
const enc = new DumbAsn1Encoder();
|
|
701
|
-
const isPrivate = jwk.d !== undefined;
|
|
702
|
-
if (isPrivate) {
|
|
703
|
-
enc.zero();
|
|
704
|
-
const enc$1 = new DumbAsn1Encoder();
|
|
705
|
-
enc$1.oidFor(jwk.crv);
|
|
706
|
-
enc.add(enc$1.end());
|
|
707
|
-
const enc$2 = new DumbAsn1Encoder();
|
|
708
|
-
enc$2.octStr(Buffer$1.from(jwk.d, 'base64'));
|
|
709
|
-
const f = enc$2.end(Buffer$1.from([0x04]));
|
|
710
|
-
enc.add(f);
|
|
711
|
-
const der = enc.end();
|
|
712
|
-
return createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
|
|
713
|
-
}
|
|
714
|
-
const enc$1 = new DumbAsn1Encoder();
|
|
715
|
-
enc$1.oidFor(jwk.crv);
|
|
716
|
-
enc.add(enc$1.end());
|
|
717
|
-
enc.bitStr(Buffer$1.from(jwk.x, 'base64'));
|
|
718
|
-
const der = enc.end();
|
|
719
|
-
return createPublicKey({ key: der, format: 'der', type: 'spki' });
|
|
720
|
-
}
|
|
721
|
-
default:
|
|
722
|
-
throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value');
|
|
723
|
-
}
|
|
430
|
+
return (jwk.d ? createPrivateKey : createPublicKey)({ format: 'jwk', key: jwk });
|
|
724
431
|
};
|
|
725
432
|
|
|
726
|
-
async function importJWK(jwk, alg
|
|
727
|
-
var _a;
|
|
433
|
+
async function importJWK(jwk, alg) {
|
|
728
434
|
if (!isObject(jwk)) {
|
|
729
435
|
throw new TypeError('JWK must be an object');
|
|
730
436
|
}
|
|
731
|
-
alg
|
|
732
|
-
if (typeof alg !== 'string' || !alg) {
|
|
733
|
-
throw new TypeError('"alg" argument is required when "jwk.alg" is not present');
|
|
734
|
-
}
|
|
437
|
+
alg ||= jwk.alg;
|
|
735
438
|
switch (jwk.kty) {
|
|
736
439
|
case 'oct':
|
|
737
440
|
if (typeof jwk.k !== 'string' || !jwk.k) {
|
|
738
441
|
throw new TypeError('missing "k" (Key Value) Parameter value');
|
|
739
442
|
}
|
|
740
|
-
octAsKeyObject !== null && octAsKeyObject !== void 0 ? octAsKeyObject : (octAsKeyObject = jwk.ext !== true);
|
|
741
|
-
if (octAsKeyObject) {
|
|
742
|
-
return parse({ ...jwk, alg, ext: (_a = jwk.ext) !== null && _a !== void 0 ? _a : false });
|
|
743
|
-
}
|
|
744
443
|
return decode(jwk.k);
|
|
745
444
|
case 'RSA':
|
|
746
445
|
if (jwk.oth !== undefined) {
|
|
@@ -863,12 +562,6 @@ function dsaDigest(alg) {
|
|
|
863
562
|
}
|
|
864
563
|
}
|
|
865
564
|
|
|
866
|
-
const [major$1, minor$1] = process.version
|
|
867
|
-
.slice(1)
|
|
868
|
-
.split('.')
|
|
869
|
-
.map((str) => parseInt(str, 10));
|
|
870
|
-
const electron = 'electron' in process.versions;
|
|
871
|
-
const rsaPssParams = !electron && (major$1 >= 17 || (major$1 === 16 && minor$1 >= 9));
|
|
872
565
|
const PSS = {
|
|
873
566
|
padding: constants.RSA_PKCS1_PSS_PADDING,
|
|
874
567
|
saltLength: constants.RSA_PSS_SALTLEN_DIGEST,
|
|
@@ -892,11 +585,11 @@ function keyForCrypto(alg, key) {
|
|
|
892
585
|
if (key.asymmetricKeyType !== 'rsa') {
|
|
893
586
|
throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be rsa');
|
|
894
587
|
}
|
|
895
|
-
|
|
588
|
+
checkKeyLength(key, alg);
|
|
896
589
|
return key;
|
|
897
|
-
case
|
|
898
|
-
case
|
|
899
|
-
case
|
|
590
|
+
case 'PS256':
|
|
591
|
+
case 'PS384':
|
|
592
|
+
case 'PS512':
|
|
900
593
|
if (key.asymmetricKeyType === 'rsa-pss') {
|
|
901
594
|
const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails;
|
|
902
595
|
const length = parseInt(alg.slice(-3), 10);
|
|
@@ -911,15 +604,7 @@ function keyForCrypto(alg, key) {
|
|
|
911
604
|
else if (key.asymmetricKeyType !== 'rsa') {
|
|
912
605
|
throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be rsa or rsa-pss');
|
|
913
606
|
}
|
|
914
|
-
|
|
915
|
-
return { key, ...PSS };
|
|
916
|
-
case !rsaPssParams && 'PS256':
|
|
917
|
-
case !rsaPssParams && 'PS384':
|
|
918
|
-
case !rsaPssParams && 'PS512':
|
|
919
|
-
if (key.asymmetricKeyType !== 'rsa') {
|
|
920
|
-
throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be rsa');
|
|
921
|
-
}
|
|
922
|
-
checkModulusLength(key, alg);
|
|
607
|
+
checkKeyLength(key, alg);
|
|
923
608
|
return { key, ...PSS };
|
|
924
609
|
case 'ES256':
|
|
925
610
|
case 'ES256K':
|
|
@@ -970,13 +655,7 @@ function getSignVerifyKey(alg, key, usage) {
|
|
|
970
655
|
throw new TypeError(invalidKeyInput(key, ...types, 'Uint8Array'));
|
|
971
656
|
}
|
|
972
657
|
|
|
973
|
-
|
|
974
|
-
if (crypto.sign.length > 3) {
|
|
975
|
-
oneShotSign = promisify(crypto.sign);
|
|
976
|
-
}
|
|
977
|
-
else {
|
|
978
|
-
oneShotSign = crypto.sign;
|
|
979
|
-
}
|
|
658
|
+
const oneShotSign = promisify(crypto.sign);
|
|
980
659
|
const sign = async (alg, key, data) => {
|
|
981
660
|
const keyObject = getSignVerifyKey(alg, key, 'sign');
|
|
982
661
|
if (alg.startsWith('HS')) {
|
|
@@ -987,18 +666,7 @@ const sign = async (alg, key, data) => {
|
|
|
987
666
|
return oneShotSign(dsaDigest(alg), data, keyForCrypto(alg, keyObject));
|
|
988
667
|
};
|
|
989
668
|
|
|
990
|
-
const
|
|
991
|
-
.slice(1)
|
|
992
|
-
.split('.')
|
|
993
|
-
.map((str) => parseInt(str, 10));
|
|
994
|
-
const oneShotCallbackSupported = major >= 16 || (major === 15 && minor >= 13);
|
|
995
|
-
let oneShotVerify;
|
|
996
|
-
if (crypto.verify.length > 4 && oneShotCallbackSupported) {
|
|
997
|
-
oneShotVerify = promisify(crypto.verify);
|
|
998
|
-
}
|
|
999
|
-
else {
|
|
1000
|
-
oneShotVerify = crypto.verify;
|
|
1001
|
-
}
|
|
669
|
+
const oneShotVerify = promisify(crypto.verify);
|
|
1002
670
|
const verify = async (alg, key, signature, data) => {
|
|
1003
671
|
const keyObject = getSignVerifyKey(alg, key, 'verify');
|
|
1004
672
|
if (alg.startsWith('HS')) {
|
|
@@ -1022,7 +690,6 @@ const verify = async (alg, key, signature, data) => {
|
|
|
1022
690
|
};
|
|
1023
691
|
|
|
1024
692
|
async function flattenedVerify(jws, key, options) {
|
|
1025
|
-
var _a;
|
|
1026
693
|
if (!isObject(jws)) {
|
|
1027
694
|
throw new JWSInvalid('Flattened JWS must be an object');
|
|
1028
695
|
}
|
|
@@ -1058,7 +725,7 @@ async function flattenedVerify(jws, key, options) {
|
|
|
1058
725
|
...parsedProt,
|
|
1059
726
|
...jws.header,
|
|
1060
727
|
};
|
|
1061
|
-
const extensions = validateCrit(JWSInvalid, new Map([['b64', true]]), options
|
|
728
|
+
const extensions = validateCrit(JWSInvalid, new Map([['b64', true]]), options?.crit, parsedProt, joseHeader);
|
|
1062
729
|
let b64 = true;
|
|
1063
730
|
if (extensions.has('b64')) {
|
|
1064
731
|
b64 = parsedProt.b64;
|
|
@@ -1072,7 +739,7 @@ async function flattenedVerify(jws, key, options) {
|
|
|
1072
739
|
}
|
|
1073
740
|
const algorithms = options && validateAlgorithms('algorithms', options.algorithms);
|
|
1074
741
|
if (algorithms && !algorithms.has(alg)) {
|
|
1075
|
-
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter not allowed');
|
|
742
|
+
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
|
|
1076
743
|
}
|
|
1077
744
|
if (b64) {
|
|
1078
745
|
if (typeof jws.payload !== 'string') {
|
|
@@ -1088,15 +755,26 @@ async function flattenedVerify(jws, key, options) {
|
|
|
1088
755
|
resolvedKey = true;
|
|
1089
756
|
}
|
|
1090
757
|
checkKeyType(alg, key, 'verify');
|
|
1091
|
-
const data = concat(encoder.encode(
|
|
1092
|
-
|
|
758
|
+
const data = concat(encoder.encode(jws.protected ?? ''), encoder.encode('.'), typeof jws.payload === 'string' ? encoder.encode(jws.payload) : jws.payload);
|
|
759
|
+
let signature;
|
|
760
|
+
try {
|
|
761
|
+
signature = decode(jws.signature);
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
throw new JWSInvalid('Failed to base64url decode the signature');
|
|
765
|
+
}
|
|
1093
766
|
const verified = await verify(alg, key, signature, data);
|
|
1094
767
|
if (!verified) {
|
|
1095
768
|
throw new JWSSignatureVerificationFailed();
|
|
1096
769
|
}
|
|
1097
770
|
let payload;
|
|
1098
771
|
if (b64) {
|
|
1099
|
-
|
|
772
|
+
try {
|
|
773
|
+
payload = decode(jws.payload);
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
throw new JWSInvalid('Failed to base64url decode the payload');
|
|
777
|
+
}
|
|
1100
778
|
}
|
|
1101
779
|
else if (typeof jws.payload === 'string') {
|
|
1102
780
|
payload = encoder.encode(jws.payload);
|
|
@@ -1209,15 +887,26 @@ var jwtPayload = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
1209
887
|
if (!isObject(payload)) {
|
|
1210
888
|
throw new JWTInvalid('JWT Claims Set must be a top-level JSON object');
|
|
1211
889
|
}
|
|
1212
|
-
const { issuer } = options;
|
|
890
|
+
const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options;
|
|
891
|
+
if (maxTokenAge !== undefined)
|
|
892
|
+
requiredClaims.push('iat');
|
|
893
|
+
if (audience !== undefined)
|
|
894
|
+
requiredClaims.push('aud');
|
|
895
|
+
if (subject !== undefined)
|
|
896
|
+
requiredClaims.push('sub');
|
|
897
|
+
if (issuer !== undefined)
|
|
898
|
+
requiredClaims.push('iss');
|
|
899
|
+
for (const claim of new Set(requiredClaims.reverse())) {
|
|
900
|
+
if (!(claim in payload)) {
|
|
901
|
+
throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, claim, 'missing');
|
|
902
|
+
}
|
|
903
|
+
}
|
|
1213
904
|
if (issuer && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) {
|
|
1214
905
|
throw new JWTClaimValidationFailed('unexpected "iss" claim value', 'iss', 'check_failed');
|
|
1215
906
|
}
|
|
1216
|
-
const { subject } = options;
|
|
1217
907
|
if (subject && payload.sub !== subject) {
|
|
1218
908
|
throw new JWTClaimValidationFailed('unexpected "sub" claim value', 'sub', 'check_failed');
|
|
1219
909
|
}
|
|
1220
|
-
const { audience } = options;
|
|
1221
910
|
if (audience &&
|
|
1222
911
|
!checkAudiencePresence(payload.aud, typeof audience === 'string' ? [audience] : audience)) {
|
|
1223
912
|
throw new JWTClaimValidationFailed('unexpected "aud" claim value', 'aud', 'check_failed');
|
|
@@ -1238,7 +927,7 @@ var jwtPayload = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
1238
927
|
}
|
|
1239
928
|
const { currentDate } = options;
|
|
1240
929
|
const now = epoch(currentDate || new Date());
|
|
1241
|
-
if ((payload.iat !== undefined ||
|
|
930
|
+
if ((payload.iat !== undefined || maxTokenAge) && typeof payload.iat !== 'number') {
|
|
1242
931
|
throw new JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid');
|
|
1243
932
|
}
|
|
1244
933
|
if (payload.nbf !== undefined) {
|
|
@@ -1257,9 +946,9 @@ var jwtPayload = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
1257
946
|
throw new JWTExpired('"exp" claim timestamp check failed', 'exp', 'check_failed');
|
|
1258
947
|
}
|
|
1259
948
|
}
|
|
1260
|
-
if (
|
|
949
|
+
if (maxTokenAge) {
|
|
1261
950
|
const age = now - payload.iat;
|
|
1262
|
-
const max = typeof
|
|
951
|
+
const max = typeof maxTokenAge === 'number' ? maxTokenAge : secs(maxTokenAge);
|
|
1263
952
|
if (age - tolerance > max) {
|
|
1264
953
|
throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', 'iat', 'check_failed');
|
|
1265
954
|
}
|
|
@@ -1271,9 +960,8 @@ var jwtPayload = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
1271
960
|
};
|
|
1272
961
|
|
|
1273
962
|
async function jwtVerify(jwt, key, options) {
|
|
1274
|
-
var _a;
|
|
1275
963
|
const verified = await compactVerify(jwt, key, options);
|
|
1276
|
-
if (
|
|
964
|
+
if (verified.protectedHeader.crit?.includes('b64') && verified.protectedHeader.b64 === false) {
|
|
1277
965
|
throw new JWTInvalid('JWTs MUST NOT use unencoded payload');
|
|
1278
966
|
}
|
|
1279
967
|
const payload = jwtPayload(verified.protectedHeader, verified.payload, options);
|
|
@@ -1313,15 +1001,16 @@ function clone(obj) {
|
|
|
1313
1001
|
return JSON.parse(JSON.stringify(obj));
|
|
1314
1002
|
}
|
|
1315
1003
|
class LocalJWKSet {
|
|
1004
|
+
_jwks;
|
|
1005
|
+
_cached = new WeakMap();
|
|
1316
1006
|
constructor(jwks) {
|
|
1317
|
-
this._cached = new WeakMap();
|
|
1318
1007
|
if (!isJWKSLike(jwks)) {
|
|
1319
1008
|
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
1320
1009
|
}
|
|
1321
1010
|
this._jwks = clone(jwks);
|
|
1322
1011
|
}
|
|
1323
1012
|
async getKey(protectedHeader, token) {
|
|
1324
|
-
const { alg, kid } = { ...protectedHeader, ...token
|
|
1013
|
+
const { alg, kid } = { ...protectedHeader, ...token?.header };
|
|
1325
1014
|
const kty = getKtyFromAlg(alg);
|
|
1326
1015
|
const candidates = this._jwks.keys.filter((jwk) => {
|
|
1327
1016
|
let candidate = kty === jwk.kty;
|
|
@@ -1363,18 +1052,33 @@ class LocalJWKSet {
|
|
|
1363
1052
|
throw new JWKSNoMatchingKey();
|
|
1364
1053
|
}
|
|
1365
1054
|
else if (length !== 1) {
|
|
1366
|
-
|
|
1055
|
+
const error = new JWKSMultipleMatchingKeys();
|
|
1056
|
+
const { _cached } = this;
|
|
1057
|
+
error[Symbol.asyncIterator] = async function* () {
|
|
1058
|
+
for (const jwk of candidates) {
|
|
1059
|
+
try {
|
|
1060
|
+
yield await importWithAlgCache(_cached, jwk, alg);
|
|
1061
|
+
}
|
|
1062
|
+
catch {
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
throw error;
|
|
1367
1068
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1069
|
+
return importWithAlgCache(this._cached, jwk, alg);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async function importWithAlgCache(cache, jwk, alg) {
|
|
1073
|
+
const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
|
|
1074
|
+
if (cached[alg] === undefined) {
|
|
1075
|
+
const key = await importJWK({ ...jwk, ext: true }, alg);
|
|
1076
|
+
if (key instanceof Uint8Array || key.type !== 'public') {
|
|
1077
|
+
throw new JWKSInvalid('JSON Web Key Set members must be public keys');
|
|
1375
1078
|
}
|
|
1376
|
-
|
|
1079
|
+
cached[alg] = key;
|
|
1377
1080
|
}
|
|
1081
|
+
return cached[alg];
|
|
1378
1082
|
}
|
|
1379
1083
|
|
|
1380
1084
|
const fetchJwks = async (url, timeout, options) => {
|
|
@@ -1415,7 +1119,19 @@ const fetchJwks = async (url, timeout, options) => {
|
|
|
1415
1119
|
}
|
|
1416
1120
|
};
|
|
1417
1121
|
|
|
1122
|
+
function isCloudflareWorkers() {
|
|
1123
|
+
return (typeof WebSocketPair !== 'undefined' ||
|
|
1124
|
+
(typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') ||
|
|
1125
|
+
(typeof EdgeRuntime !== 'undefined' && EdgeRuntime === 'vercel'));
|
|
1126
|
+
}
|
|
1418
1127
|
class RemoteJWKSet extends LocalJWKSet {
|
|
1128
|
+
_url;
|
|
1129
|
+
_timeoutDuration;
|
|
1130
|
+
_cooldownDuration;
|
|
1131
|
+
_cacheMaxAge;
|
|
1132
|
+
_jwksTimestamp;
|
|
1133
|
+
_pendingFetch;
|
|
1134
|
+
_options;
|
|
1419
1135
|
constructor(url, options) {
|
|
1420
1136
|
super({ keys: [] });
|
|
1421
1137
|
this._jwks = undefined;
|
|
@@ -1423,12 +1139,12 @@ class RemoteJWKSet extends LocalJWKSet {
|
|
|
1423
1139
|
throw new TypeError('url must be an instance of URL');
|
|
1424
1140
|
}
|
|
1425
1141
|
this._url = new URL(url.href);
|
|
1426
|
-
this._options = { agent: options
|
|
1142
|
+
this._options = { agent: options?.agent, headers: options?.headers };
|
|
1427
1143
|
this._timeoutDuration =
|
|
1428
|
-
typeof
|
|
1144
|
+
typeof options?.timeoutDuration === 'number' ? options?.timeoutDuration : 5000;
|
|
1429
1145
|
this._cooldownDuration =
|
|
1430
|
-
typeof
|
|
1431
|
-
this._cacheMaxAge = typeof
|
|
1146
|
+
typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000;
|
|
1147
|
+
this._cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000;
|
|
1432
1148
|
}
|
|
1433
1149
|
coolingDown() {
|
|
1434
1150
|
return typeof this._jwksTimestamp === 'number'
|
|
@@ -1459,38 +1175,29 @@ class RemoteJWKSet extends LocalJWKSet {
|
|
|
1459
1175
|
}
|
|
1460
1176
|
async reload() {
|
|
1461
1177
|
if (this._pendingFetch && isCloudflareWorkers()) {
|
|
1462
|
-
|
|
1463
|
-
const isDone = () => {
|
|
1464
|
-
if (this._pendingFetch === undefined) {
|
|
1465
|
-
resolve();
|
|
1466
|
-
}
|
|
1467
|
-
else {
|
|
1468
|
-
setTimeout(isDone, 5);
|
|
1469
|
-
}
|
|
1470
|
-
};
|
|
1471
|
-
isDone();
|
|
1472
|
-
});
|
|
1473
|
-
}
|
|
1474
|
-
if (!this._pendingFetch) {
|
|
1475
|
-
this._pendingFetch = fetchJwks(this._url, this._timeoutDuration, this._options)
|
|
1476
|
-
.then((json) => {
|
|
1477
|
-
if (!isJWKSLike(json)) {
|
|
1478
|
-
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
1479
|
-
}
|
|
1480
|
-
this._jwks = { keys: json.keys };
|
|
1481
|
-
this._jwksTimestamp = Date.now();
|
|
1482
|
-
this._pendingFetch = undefined;
|
|
1483
|
-
})
|
|
1484
|
-
.catch((err) => {
|
|
1485
|
-
this._pendingFetch = undefined;
|
|
1486
|
-
throw err;
|
|
1487
|
-
});
|
|
1178
|
+
this._pendingFetch = undefined;
|
|
1488
1179
|
}
|
|
1180
|
+
this._pendingFetch ||= fetchJwks(this._url, this._timeoutDuration, this._options)
|
|
1181
|
+
.then((json) => {
|
|
1182
|
+
if (!isJWKSLike(json)) {
|
|
1183
|
+
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
1184
|
+
}
|
|
1185
|
+
this._jwks = { keys: json.keys };
|
|
1186
|
+
this._jwksTimestamp = Date.now();
|
|
1187
|
+
this._pendingFetch = undefined;
|
|
1188
|
+
})
|
|
1189
|
+
.catch((err) => {
|
|
1190
|
+
this._pendingFetch = undefined;
|
|
1191
|
+
throw err;
|
|
1192
|
+
});
|
|
1489
1193
|
await this._pendingFetch;
|
|
1490
1194
|
}
|
|
1491
1195
|
}
|
|
1492
1196
|
function createRemoteJWKSet(url, options) {
|
|
1493
|
-
|
|
1197
|
+
const set = new RemoteJWKSet(url, options);
|
|
1198
|
+
return async function (protectedHeader, token) {
|
|
1199
|
+
return set.getKey(protectedHeader, token);
|
|
1200
|
+
};
|
|
1494
1201
|
}
|
|
1495
1202
|
|
|
1496
1203
|
// https://appleid.apple.com/.well-known/openid-configuration
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/connector-apple",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Apple web connector implementation.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@logto/connector-kit": "^
|
|
7
|
-
"@logto/shared": "^
|
|
8
|
-
"jose": "^
|
|
6
|
+
"@logto/connector-kit": "^2.0.0",
|
|
7
|
+
"@logto/shared": "^3.0.0",
|
|
8
|
+
"jose": "^5.0.0"
|
|
9
9
|
},
|
|
10
10
|
"main": "./lib/index.js",
|
|
11
11
|
"module": "./lib/index.js",
|