wkd-checker 2.1.0 → 2.3.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) {
@@ -118,15 +188,26 @@ const checkKeyUrl = (url, policy, options, email) => __awaiter(void 0, void 0, v
118
188
  const identities = yield key.getUserIDs();
119
189
  // 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
190
  // "Other User ID packets and their associated binding signatures MUST be removed before publication."
121
- if (identities.length === 1) {
122
- const id = identities[0];
123
- // robustly extract email from "Name <email>" format
191
+ result.emailInKey = identities.length > 0;
192
+ for (const id of identities) {
124
193
  const match = id.match(/<(.+)>/);
125
194
  const emailInId = match ? match[1] : id;
126
- result.emailInKey = emailInId.trim() === email;
195
+ if (emailInId.trim() !== email) {
196
+ result.emailInKey = false;
197
+ break;
198
+ }
127
199
  }
128
- else {
129
- result.emailInKey = false;
200
+ // Check policy compliance
201
+ if (((_c = result.policy) === null || _c === void 0 ? void 0 : _c.mailboxOnly) && identities.length > 0) {
202
+ for (const id of identities) {
203
+ // If mailbox-only, the ID MUST
204
+ const match = id.match(/<(.+)>/);
205
+ const emailInId = match ? match[1] : id;
206
+ const expected = [`<${emailInId.trim()}>`, emailInId.trim()];
207
+ if (!expected.includes(id.trim())) {
208
+ result.policyCompliant = false;
209
+ }
210
+ }
130
211
  }
131
212
  result.fingerprint = key.getFingerprint().toUpperCase();
132
213
  const expirationTime = yield key.getExpirationTime();
@@ -139,7 +220,8 @@ const checkKeyUrl = (url, policy, options, email) => __awaiter(void 0, void 0, v
139
220
  result.valid = result.policyAvailable &&
140
221
  result.key_available &&
141
222
  result.keyTypeValid &&
142
- result.emailInKey;
223
+ result.emailInKey &&
224
+ result.policyCompliant;
143
225
  return result;
144
226
  });
145
227
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wkd-checker",
3
- "version": "2.1.0",
3
+ "version": "2.3.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",