wkd-checker 1.2.0 → 2.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/dist/cli.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +38 -8
- package/package.json +13 -2
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ const printSection = (title, result) => {
|
|
|
35
35
|
const statusBadge = isWorking ? `${GREEN} ${statusLabel} ${RESET}` : `${RED} ${statusLabel} ${RESET}`;
|
|
36
36
|
console.log(`\n${BOLD}${title}${RESET} ${statusBadge}`);
|
|
37
37
|
console.log(`${DIM}------------------------------------------------------------${RESET}`);
|
|
38
|
+
console.log(`Policy Location: ${CYAN}${result.policy_location}${RESET}`);
|
|
38
39
|
console.log(`Policy Available: ${formatBoolean(result.policyAvailable)}`);
|
|
39
40
|
console.log(`Policy CORS Valid: ${formatBoolean(result.policyCorsValid)}`);
|
|
40
41
|
console.log(`\nKey Location: ${CYAN}${result.key_location || 'N/A'}${RESET}`);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ class WKDError extends Error {
|
|
|
28
28
|
}
|
|
29
29
|
exports.WKDError = WKDError;
|
|
30
30
|
const EMAIL_REGEX = /^[^@]+@[^@]+\.[^@]+$/;
|
|
31
|
+
const USER_AGENT = "WKD-Checker (+https://www.npmjs.com/package/wkd-checker)";
|
|
31
32
|
/**
|
|
32
33
|
* Validates an email address format.
|
|
33
34
|
* @param {string} email - The email address to validate.
|
|
@@ -44,6 +45,7 @@ exports.validateEmail = validateEmail;
|
|
|
44
45
|
const fetchWithCorsCheck = (url, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
46
|
try {
|
|
46
47
|
const response = yield fetch(url, options);
|
|
48
|
+
// Spec does not mandate CORS, but it is beneficial for web clients (e.g. webmail usage). Not factored into validity.
|
|
47
49
|
const corsValid = response.headers.get("access-control-allow-origin") === "*";
|
|
48
50
|
return { response: response.ok ? response : null, corsValid };
|
|
49
51
|
}
|
|
@@ -62,12 +64,14 @@ const detectKeyType = (keyData) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
62
64
|
const binaryKey = new Uint8Array(buffer);
|
|
63
65
|
const header = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
|
|
64
66
|
const prefix = new TextDecoder().decode(binaryKey.slice(0, header.length));
|
|
67
|
+
// Spec Section 3.1: "The server SHOULD use "application/octet-stream" as the Content-Type for the data but clients SHOULD also accept any other Content-Type. The server SHOULD NOT return an ASCII armored version of the key."
|
|
65
68
|
if (prefix === header) {
|
|
66
69
|
const text = new TextDecoder().decode(binaryKey);
|
|
67
70
|
const key = yield (0, openpgp_1.readKey)({ armoredKey: text });
|
|
68
71
|
return { keyType: KeyType.ArmoredKey, key };
|
|
69
72
|
}
|
|
70
73
|
else {
|
|
74
|
+
// Spec Section 3.1: "The HTTP GET method MUST return the binary representation of the OpenPGP key for the given mail address."
|
|
71
75
|
const key = yield (0, openpgp_1.readKey)({ binaryKey });
|
|
72
76
|
return { keyType: KeyType.BinaryKey, key };
|
|
73
77
|
}
|
|
@@ -85,15 +89,16 @@ const detectKeyType = (keyData) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
85
89
|
* @returns {Promise<KeyCheckResult>} The result of the key check.
|
|
86
90
|
*/
|
|
87
91
|
const checkKeyUrl = (url, policy, options, email) => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
-
var _a;
|
|
92
|
+
var _a, _b;
|
|
89
93
|
const [policyData, keyData] = yield Promise.all([
|
|
90
94
|
fetchWithCorsCheck(policy, options),
|
|
91
95
|
fetchWithCorsCheck(url, options)
|
|
92
96
|
]);
|
|
93
97
|
const result = {
|
|
98
|
+
policy_location: ((_a = policyData.response) === null || _a === void 0 ? void 0 : _a.url) || policy,
|
|
94
99
|
policyAvailable: !!policyData.response,
|
|
95
100
|
policyCorsValid: policyData.corsValid,
|
|
96
|
-
key_location: ((
|
|
101
|
+
key_location: ((_b = keyData.response) === null || _b === void 0 ? void 0 : _b.url) || url,
|
|
97
102
|
key_available: !!keyData.response,
|
|
98
103
|
keyCorsValid: keyData.corsValid,
|
|
99
104
|
keyType: KeyType.Invalid,
|
|
@@ -105,19 +110,36 @@ const checkKeyUrl = (url, policy, options, email) => __awaiter(void 0, void 0, v
|
|
|
105
110
|
if (keyData.response) {
|
|
106
111
|
const { keyType, key } = yield detectKeyType(keyData.response);
|
|
107
112
|
result.keyType = keyType;
|
|
113
|
+
// The HTTP GET method MUST return the binary representation of the OpenPGP key for the given mail address.
|
|
108
114
|
result.keyTypeValid = keyType === KeyType.BinaryKey;
|
|
109
115
|
if (key) {
|
|
110
116
|
const identities = yield key.getUserIDs();
|
|
111
|
-
|
|
117
|
+
// Spec Section 5: "The mail provider MUST make sure to publish a key in a way that only the mail address belonging to the requested user is part of the User ID packets included in the returned key."
|
|
118
|
+
// "Other User ID packets and their associated binding signatures MUST be removed before publication."
|
|
119
|
+
if (identities.length === 1) {
|
|
120
|
+
const id = identities[0];
|
|
121
|
+
// robustly extract email from "Name <email>" format
|
|
122
|
+
const match = id.match(/<(.+)>/);
|
|
123
|
+
const emailInId = match ? match[1] : id;
|
|
124
|
+
result.emailInKey = emailInId.trim() === email;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
result.emailInKey = false;
|
|
128
|
+
}
|
|
112
129
|
result.fingerprint = key.getFingerprint().toUpperCase();
|
|
113
130
|
}
|
|
114
131
|
}
|
|
115
|
-
result.valid = result.policyAvailable &&
|
|
132
|
+
result.valid = result.policyAvailable &&
|
|
133
|
+
result.key_available &&
|
|
134
|
+
result.keyTypeValid &&
|
|
135
|
+
result.emailInKey;
|
|
116
136
|
return result;
|
|
117
137
|
});
|
|
118
138
|
/**
|
|
119
139
|
* Encode input using Z-Base32 encoding.
|
|
120
140
|
*
|
|
141
|
+
* Spec Section 3.1: "The resulting 160 bit digest is encoded using the Z-Base-32 method as described in [RFC6189], section 5.1.6."
|
|
142
|
+
*
|
|
121
143
|
* @param {Uint8Array} data - The binary data to encode
|
|
122
144
|
* @returns {String} Binary data encoded using Z-Base32.
|
|
123
145
|
*/
|
|
@@ -152,21 +174,27 @@ function zbase32Encode(data) {
|
|
|
152
174
|
}
|
|
153
175
|
/**
|
|
154
176
|
* Generates WKD URLs for a given email address.
|
|
177
|
+
* Spec Section 3.1: "Key Discovery"
|
|
155
178
|
* @param {string} email - The email address.
|
|
156
179
|
* @returns {Promise<{ advancedUrl: string, directUrl: string, advancedPolicyUrl: string, directPolicyUrl: string }>} The generated URLs.
|
|
157
180
|
*/
|
|
158
181
|
const generateWkdUrls = (email) => __awaiter(void 0, void 0, void 0, function* () {
|
|
159
182
|
const [localPart, domain] = email.split('@');
|
|
160
|
-
// Spec
|
|
183
|
+
// Spec Section 3.1: "all upper-case ASCII characters in a User ID are mapped to lowercase. Non-ASCII characters are not changed."
|
|
161
184
|
const localPartHashInput = localPart.replace(/[A-Z]/g, c => c.toLowerCase());
|
|
162
185
|
const domainLower = domain.toLowerCase();
|
|
186
|
+
// Spec Section 3.1: "The so mapped local-part is hashed using the SHA-1 algorithm."
|
|
163
187
|
const hashBuffer = yield crypto.subtle.digest("SHA-1", new TextEncoder().encode(localPartHashInput));
|
|
188
|
+
// Spec Section 3.1: "The resulting 160 bit digest is encoded using the Z-Base-32 method as described in [RFC6189], section 5.1.6. The resulting string has a fixed length of 32 octets."
|
|
164
189
|
const hash = zbase32Encode(new Uint8Array(hashBuffer));
|
|
165
190
|
const nameParam = encodeURIComponent(localPart);
|
|
166
191
|
return {
|
|
192
|
+
// Spec Section 3.1: Advanced Method URI Construction
|
|
167
193
|
advancedUrl: `https://openpgpkey.${domainLower}/.well-known/openpgpkey/${domainLower}/hu/${hash}?l=${nameParam}`,
|
|
194
|
+
// Spec Section 3.1: Direct Method URI Construction
|
|
168
195
|
directUrl: `https://${domainLower}/.well-known/openpgpkey/hu/${hash}?l=${nameParam}`,
|
|
169
|
-
|
|
196
|
+
// Spec Section 4.5: Policy Flags
|
|
197
|
+
advancedPolicyUrl: `https://openpgpkey.${domainLower}/.well-known/openpgpkey/${domainLower}/policy`,
|
|
170
198
|
directPolicyUrl: `https://${domainLower}/.well-known/openpgpkey/policy`
|
|
171
199
|
};
|
|
172
200
|
});
|
|
@@ -180,11 +208,13 @@ const getKey = (email) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
180
208
|
throw new WKDError('Invalid e-mail address.');
|
|
181
209
|
}
|
|
182
210
|
const { advancedUrl, directUrl } = yield generateWkdUrls(email);
|
|
211
|
+
// Spec Section 3.1: "Implementations MUST first try the advanced method. Only if an address for the required sub-domain does not exist, they SHOULD fall back to the direct method."
|
|
183
212
|
const urls = [advancedUrl, directUrl];
|
|
184
213
|
for (const url of urls) {
|
|
185
214
|
try {
|
|
186
|
-
const response = yield fetch(url);
|
|
215
|
+
const response = yield fetch(url, { headers: { 'User-Agent': USER_AGENT } });
|
|
187
216
|
if (response.ok) {
|
|
217
|
+
// Spec Section 3.1
|
|
188
218
|
return new Uint8Array(yield response.arrayBuffer());
|
|
189
219
|
}
|
|
190
220
|
}
|
|
@@ -203,7 +233,7 @@ exports.getKey = getKey;
|
|
|
203
233
|
const checkKey = (email) => __awaiter(void 0, void 0, void 0, function* () {
|
|
204
234
|
const { advancedUrl, directUrl, advancedPolicyUrl, directPolicyUrl } = yield generateWkdUrls(email);
|
|
205
235
|
const options = {
|
|
206
|
-
headers: { "User-Agent":
|
|
236
|
+
headers: { "User-Agent": USER_AGENT }
|
|
207
237
|
};
|
|
208
238
|
const [advancedResult, directResult] = yield Promise.all([
|
|
209
239
|
checkKeyUrl(advancedUrl, advancedPolicyUrl, options, email),
|
package/package.json
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wkd-checker",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Web Key Directory library for Node.js. Retreive OpenPGP keys from WKD servers and validate them.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/JonatanMGit/wkd-checker.git"
|
|
7
|
+
"url": "git+https://github.com/JonatanMGit/wkd-checker.git"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"wkd",
|
|
11
|
+
"openpgp",
|
|
12
|
+
"pgp",
|
|
13
|
+
"gpg",
|
|
14
|
+
"keyserver"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://miarecki.eu/tools/wkd-checker/",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/JonatanMGit/wkd-checker/issues"
|
|
8
19
|
},
|
|
9
20
|
"bin": {
|
|
10
21
|
"wkd-checker": "dist/cli.js"
|