| … | … | @@ -207,3 +207,39 @@ test('createMessage with string content', (t) => { |
| 207 | 207 | t.ok(verified.verified) |
| 208 | 208 | t.is(verified.content, 'plain string content') |
| 209 | 209 | }) |
| 210 | |
| 211 | // ⛔ REGRESSION: the timestamp match must be EXACTLY 13 digits. |
| 212 | // |
| 213 | // The signed message is `timestamp + base64(sha256(challenge))`. A greedy `\d+` swallows the |
| 214 | // hash's first character whenever it is a digit — 10 of base64's 64 symbols — so roughly one in |
| 215 | // six verifications failed as "hash mismatch". Measured 59/300 before the fix, 0/500 after. |
| 216 | // |
| 217 | // It is deterministic per challenge, not flaky: the same challenge always produces the same hash, |
| 218 | // so a given challenge either always worked or never did. That is what made it survive — it looks |
| 219 | // like a bad client rather than a broken parser. |
| 220 | // |
| 221 | // The canonical `anproto` package (lib/an.js:119) has always used `/^(\d{13})(.+)$/`, which is why |
| 222 | // pear-git-v3 was unaffected: it depends on that package, and this file is a copy that drifted. |
| 223 | test('a hash beginning with a digit still verifies', (t) => { |
| 224 | const kp = ANProto.gen() |
| 225 | |
| 226 | // Find a challenge whose hash starts with a digit — the case that used to fail. |
| 227 | let challenge = null |
| 228 | for (let i = 0; i < 200 && !challenge; i++) { |
| 229 | const c = 'challenge-' + i |
| 230 | if (/^[0-9]/.test(ANProto.hash(c))) challenge = c |
| 231 | } |
| 232 | t.ok(challenge, 'found one — they are ~15% of all challenges') |
| 233 | |
| 234 | const r = ANProto.verify(challenge, ANProto.sign(challenge, kp)) |
| 235 | t.ok(r.valid, 'verifies: ' + (r.error || 'ok')) |
| 236 | t.is(ANProto.open(ANProto.sign(challenge, kp)).hash, ANProto.hash(challenge), |
| 237 | 'and the parsed hash is whole, not one character short') |
| 238 | }) |
| 239 | |
| 240 | test('the timestamp is parsed as exactly 13 digits', (t) => { |
| 241 | const kp = ANProto.gen() |
| 242 | const opened = ANProto.open(ANProto.sign('x', kp)) |
| 243 | t.is(String(opened.timestamp).length, 13, 'never 14 — a 14th digit means it ate the hash') |
| 244 | t.is(opened.hash.length, 44, 'base64 of sha256 is always 44 characters') |
| 245 | }) |