wkd-checker 2.2.0 → 2.4.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/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export interface KeyCheckResult {
5
5
  policy_location: string;
6
6
  policyAvailable: boolean;
7
7
  policyCorsValid: boolean;
8
+ policy: PolicyFlags | null;
8
9
  key_location: string | null;
9
10
  key_available: boolean;
10
11
  keyCorsValid: boolean;
@@ -14,6 +15,7 @@ export interface KeyCheckResult {
14
15
  emailInKey: boolean;
15
16
  expired: boolean;
16
17
  revoked: boolean;
18
+ policyCompliant: boolean;
17
19
  valid: boolean;
18
20
  }
19
21
  /**
@@ -24,6 +26,20 @@ export declare enum KeyType {
24
26
  BinaryKey = "BinaryKey",
25
27
  ArmoredKey = "ArmoredKey"
26
28
  }
29
+ /**
30
+ * Result of the policy file check.
31
+ */
32
+ export interface PolicyFlags {
33
+ mailboxOnly: boolean;
34
+ daneOnly: boolean;
35
+ authSubmit: boolean;
36
+ protocolVersion: number | null;
37
+ submissionAddress: string | null;
38
+ others: Array<{
39
+ key: string;
40
+ value: string | null;
41
+ }>;
42
+ }
27
43
  export declare class WKDError extends Error {
28
44
  constructor(message: string);
29
45
  }
package/dist/index.js CHANGED
@@ -80,6 +80,64 @@ const detectKeyType = (keyData) => __awaiter(void 0, void 0, void 0, function* (
80
80
  return { keyType: KeyType.Invalid, key: null };
81
81
  }
82
82
  });
83
+ /**
84
+ * Parses the Policy Flags file.
85
+ * Section 4.5. Policy Flags
86
+ * @param {string} text - Content of the policy file.
87
+ * @returns {PolicyFlags} Parsed policy flags.
88
+ */
89
+ const parsePolicy = (text) => {
90
+ const flags = {
91
+ mailboxOnly: false,
92
+ daneOnly: false,
93
+ authSubmit: false,
94
+ protocolVersion: null,
95
+ submissionAddress: null,
96
+ others: []
97
+ };
98
+ if (!text)
99
+ return flags;
100
+ const lines = text.split(/\r?\n/);
101
+ for (const line of lines) {
102
+ const trimmed = line.trim();
103
+ // "Empty lines and lines starting with a '#' character are considered comment lines."
104
+ if (!trimmed || trimmed.startsWith('#'))
105
+ continue;
106
+ const colonIndex = trimmed.indexOf(':');
107
+ let key = '';
108
+ let value = null;
109
+ if (colonIndex !== -1) {
110
+ key = trimmed.substring(0, colonIndex).trim().toLowerCase();
111
+ value = trimmed.substring(colonIndex + 1).trim();
112
+ }
113
+ else {
114
+ key = trimmed.trim().toLowerCase();
115
+ }
116
+ switch (key) {
117
+ case 'mailbox-only':
118
+ flags.mailboxOnly = true;
119
+ break;
120
+ case 'dane-only':
121
+ flags.daneOnly = true;
122
+ break;
123
+ case 'auth-submit':
124
+ flags.authSubmit = true;
125
+ break;
126
+ case 'protocol-version':
127
+ if (value)
128
+ flags.protocolVersion = parseInt(value, 10);
129
+ break;
130
+ case 'submission-address':
131
+ if (value)
132
+ flags.submissionAddress = value;
133
+ break;
134
+ default:
135
+ // "For experimental use of new features or for provider specific settings, keywords MUST be prefixed with a domain name and an underscore."
136
+ flags.others.push({ key, value });
137
+ }
138
+ }
139
+ return flags;
140
+ };
83
141
  /**
84
142
  * Checks for a key at a specific WKD URL.
85
143
  * @param {string} url - The WKD URL for the key.
@@ -89,15 +147,26 @@ const detectKeyType = (keyData) => __awaiter(void 0, void 0, void 0, function* (
89
147
  * @returns {Promise<KeyCheckResult>} The result of the key check.
90
148
  */
91
149
  const checkKeyUrl = (url, policy, options, email) => __awaiter(void 0, void 0, void 0, function* () {
92
- var _a, _b;
150
+ var _a, _b, _c;
93
151
  const [policyData, keyData] = yield Promise.all([
94
152
  fetchWithCorsCheck(policy, options),
95
153
  fetchWithCorsCheck(url, options)
96
154
  ]);
155
+ let policyFlags = null;
156
+ if (policyData.response) {
157
+ try {
158
+ const policyText = yield policyData.response.text();
159
+ policyFlags = parsePolicy(policyText);
160
+ }
161
+ catch (_d) {
162
+ policyFlags = parsePolicy("");
163
+ }
164
+ }
97
165
  const result = {
98
166
  policy_location: ((_a = policyData.response) === null || _a === void 0 ? void 0 : _a.url) || policy,
99
167
  policyAvailable: !!policyData.response,
100
168
  policyCorsValid: policyData.corsValid,
169
+ policy: policyFlags,
101
170
  key_location: ((_b = keyData.response) === null || _b === void 0 ? void 0 : _b.url) || url,
102
171
  key_available: !!keyData.response,
103
172
  keyCorsValid: keyData.corsValid,
@@ -107,6 +176,7 @@ const checkKeyUrl = (url, policy, options, email) => __awaiter(void 0, void 0, v
107
176
  emailInKey: false,
108
177
  expired: false,
109
178
  revoked: false,
179
+ policyCompliant: true,
110
180
  valid: false,
111
181
  };
112
182
  if (keyData.response) {
@@ -116,15 +186,25 @@ const checkKeyUrl = (url, policy, options, email) => __awaiter(void 0, void 0, v
116
186
  result.keyTypeValid = keyType === KeyType.BinaryKey;
117
187
  if (key) {
118
188
  const identities = yield key.getUserIDs();
119
- // 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."
120
- // "Other User ID packets and their associated binding signatures MUST be removed before publication."
121
- result.emailInKey = identities.length > 0;
122
- for (const id of identities) {
123
- const match = id.match(/<(.+)>/);
124
- const emailInId = match ? match[1] : id;
125
- if (emailInId.trim() !== email) {
126
- result.emailInKey = false;
127
- break;
189
+ // Helper to extract email address from User ID string ("Name <email@domain>" or "email@domain")
190
+ const extractEmail = (id) => {
191
+ const match = id.match(/<([^>]+)>/);
192
+ return (match ? match[1] : id).trim().toLowerCase();
193
+ };
194
+ const targetEmail = email.trim().toLowerCase();
195
+ const emailsInKey = identities.map(extractEmail);
196
+ // Spec Section 3.1 & Section 5: The key needs to carry a User ID packet with that mail address.
197
+ // Client MUST check that the received key has the requested User-Id and MUST drop all other User-Ids.
198
+ result.emailInKey = emailsInKey.includes(targetEmail);
199
+ // Check policy compliance
200
+ if (((_c = result.policy) === null || _c === void 0 ? void 0 : _c.mailboxOnly) && identities.length > 0) {
201
+ for (const id of identities) {
202
+ const emailInId = extractEmail(id);
203
+ const expected = [`<${emailInId}>`, emailInId];
204
+ if (!expected.includes(id.trim().toLowerCase())) {
205
+ result.policyCompliant = false;
206
+ break;
207
+ }
128
208
  }
129
209
  }
130
210
  result.fingerprint = key.getFingerprint().toUpperCase();
@@ -138,7 +218,8 @@ const checkKeyUrl = (url, policy, options, email) => __awaiter(void 0, void 0, v
138
218
  result.valid = result.policyAvailable &&
139
219
  result.key_available &&
140
220
  result.keyTypeValid &&
141
- result.emailInKey;
221
+ result.emailInKey &&
222
+ result.policyCompliant;
142
223
  return result;
143
224
  });
144
225
  /**
@@ -237,6 +318,9 @@ exports.getKey = getKey;
237
318
  * @returns {Promise<{ advanced: KeyCheckResult, direct: KeyCheckResult }>} Results for both lookup methods.
238
319
  */
239
320
  const checkKey = (email) => __awaiter(void 0, void 0, void 0, function* () {
321
+ if (!(0, exports.validateEmail)(email)) {
322
+ throw new WKDError('Invalid e-mail address.');
323
+ }
240
324
  const { advancedUrl, directUrl, advancedPolicyUrl, directPolicyUrl } = yield generateWkdUrls(email);
241
325
  const options = {
242
326
  headers: { "User-Agent": USER_AGENT }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wkd-checker",
3
- "version": "2.2.0",
3
+ "version": "2.4.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",