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 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
@@ -2,6 +2,7 @@
2
2
  * Result of a key check operation.
3
3
  */
4
4
  export interface KeyCheckResult {
5
+ policy_location: string;
5
6
  policyAvailable: boolean;
6
7
  policyCorsValid: boolean;
7
8
  key_location: string | null;
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: ((_a = keyData.response) === null || _a === void 0 ? void 0 : _a.url) || url,
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
- result.emailInKey = identities.some(identity => identity.includes(email));
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 && result.policyCorsValid && result.key_available && result.keyCorsValid && result.keyTypeValid && result.emailInKey;
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 Requirement: map uppercase ASCII to lowercase. Non-ASCII are not changed.
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
- advancedPolicyUrl: `https://openpgpkey.${domainLower}/.well-known/openpgpkey/policy`,
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": "WKD-Checker (+https://miarecki.eu/posts/web-key-directory-setup/)" }
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": "1.2.0",
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"