Skip to content

Commit 1abe5d1

Browse files
Test coverage expanded
1 parent 869c90b commit 1abe5d1

File tree

5 files changed

+102
-17
lines changed

5 files changed

+102
-17
lines changed

src/auth/certificates/MasterCertificate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export class MasterCertificate extends Certificate {
257257
const certificate = new MasterCertificate(
258258
certificateType,
259259
finalSerialNumber,
260-
subject,
260+
subjectIdentityKey,
261261
(await certifierWallet.getPublicKey({ identityKey: true })).publicKey,
262262
revocationOutpoint,
263263
certificateFields,

src/auth/certificates/__tests/MasterCertificate.test.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ describe('MasterCertificate', () => {
356356
expect(newCert.fields[fieldName]).toMatch(/^[A-Za-z0-9+/]+=*$/)
357357
}
358358
})
359+
359360
it('should allow issuing a self-signed certificate and decrypt it with the same wallet', async () => {
360361
const subjectWallet = new CompletedProtoWallet(subjectKey2)
361362

@@ -386,38 +387,38 @@ describe('MasterCertificate', () => {
386387

387388
expect(decrypted).toEqual(selfSignedFields)
388389
})
389-
})
390390

391-
describe('issueCertificateForSubject subject identity resolution', () => {
392391
it('resolves subject === "self" to the certifier wallet identity key', async () => {
393-
const certifierIdentity = (
392+
const certifierWallet = new CompletedProtoWallet(new PrivateKey(99))
393+
394+
const certifierIdentityKey = (
394395
await certifierWallet.getPublicKey({ identityKey: true })
395396
).publicKey
396397

397-
const fields = { foo: 'bar' }
398-
399398
const cert = await MasterCertificate.issueCertificateForSubject(
400399
certifierWallet,
401400
'self',
402-
fields,
401+
{ name: 'Alice' },
403402
'TEST_CERT'
404403
)
405404

406-
expect(cert.subject).toBe(certifierIdentity)
405+
expect(cert.subject).toBe(certifierIdentityKey)
407406
})
408407

409408
it('uses provided subjectIdentityKey when subject is a valid hex string', async () => {
410-
const providedSubject = 'a'.repeat(64); // valid 32-byte hex
409+
const certifierWallet = new CompletedProtoWallet(new PrivateKey(42))
411410

412-
const fields = { foo: 'bar' }
411+
const validPubkey =
412+
'0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'
413413

414-
const cert = await MasterCertificate.issueCertificateForSubject(
415-
certifierWallet,
416-
providedSubject,
417-
fields,
418-
'TEST_CERT'
419-
)
414+
const cert = await MasterCertificate.issueCertificateForSubject(
415+
certifierWallet,
416+
validPubkey,
417+
{ name: 'Alice' },
418+
'TEST_CERT'
419+
)
420420

421-
expect(cert.subject).toBe(providedSubject)
421+
expect(cert.subject).toBe(validPubkey)
422+
})
422423
})
423424
})

src/primitives/__tests/Hash.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as hash from '../../primitives/Hash'
33
import * as crypto from 'crypto'
44
import PBKDF2Vectors from './PBKDF2.vectors'
55
import { toArray, toHex } from '../../primitives/utils'
6+
import { SHA1 } from '../..//primitives/Hash'
67

78
describe('Hash', function () {
89
function test (Hash, cases): void {
@@ -177,4 +178,27 @@ describe('Hash', function () {
177178
})
178179
}
179180
})
181+
182+
describe('Hash strict length validation (TOB-21)', () => {
183+
184+
it('throws when pendingTotal is not a safe integer', () => {
185+
const h = new SHA1()
186+
187+
h.pendingTotal = Number.MAX_SAFE_INTEGER + 10
188+
189+
expect(() => {
190+
h.digest()
191+
}).toThrow('Message too long for this hash function')
192+
})
193+
194+
it('throws when pendingTotal is negative', () => {
195+
const h = new SHA1()
196+
197+
h.pendingTotal = -5
198+
199+
expect(() => {
200+
h.digest()
201+
}).toThrow('Message too long for this hash function')
202+
})
203+
})
180204
})

src/primitives/__tests/utils.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,60 @@ describe('verifyNotNull', () => {
320320
expect(() => verifyNotNull(undefined, 'Another custom error')).toThrow('Another custom error')
321321
})
322322
})
323+
324+
describe('toUTF8 strict UTF-8 decoding (TOB-21)', () => {
325+
326+
//
327+
// 1. Replacement for invalid 2-byte continuation
328+
//
329+
it('replaces invalid 2-byte sequences with U+FFFD', () => {
330+
// 0xC2 should expect a continuation byte 0x80–0xBF
331+
const arr = [0xC2, 0x20] // 0x20 is INVALID continuation
332+
const str = toUTF8(arr)
333+
expect(str).toBe('\uFFFD')
334+
})
335+
336+
//
337+
// 2. Valid 3-byte UTF-8 (e.g., "€")
338+
//
339+
it('decodes valid 3-byte sequences', () => {
340+
const euro = [0xE2, 0x82, 0xAC]
341+
expect(toUTF8(euro)).toBe('€')
342+
})
343+
344+
//
345+
// 3. Replacement for invalid 3-byte sequence
346+
//
347+
it('replaces invalid 3-byte sequences', () => {
348+
// Middle byte invalid
349+
const arr = [0xE2, 0x20, 0xAC]
350+
expect(toUTF8(arr)).toBe('\uFFFD')
351+
})
352+
353+
//
354+
// 4. Valid 4-byte UTF-8 (e.g., U+1F600 😀)
355+
//
356+
it('decodes valid 4-byte sequences into surrogate pairs', () => {
357+
const smile = [0xF0, 0x9F, 0x98, 0x80] // 😀
358+
expect(toUTF8(smile)).toBe('😀')
359+
})
360+
361+
//
362+
// 5. Replacement for invalid 4-byte sequence
363+
//
364+
it('replaces invalid 4-byte sequences with U+FFFD', () => {
365+
// 0x9F is valid, 0x20 is INVALID continuation for byte 3
366+
const arr = [0xF0, 0x9F, 0x20, 0x80]
367+
expect(toUTF8(arr)).toBe('\uFFFD')
368+
})
369+
370+
//
371+
// 6. Replacement of incomplete sequences at end of array
372+
//
373+
it('replaces incomplete UTF-8 sequence at end', () => {
374+
const arr = [0xE2] // incomplete 3-byte seq
375+
expect(toUTF8(arr)).toBe('\uFFFD')
376+
})
377+
378+
})
379+

src/primitives/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ export const toUTF8 = (arr: number[]): string => {
242242
const byte2 = arr[i + 1]
243243
if ((byte2 & 0xc0) !== 0x80) {
244244
emitReplacement()
245+
i += 1
245246
continue
246247
}
247248
const codePoint = ((byte1 & 0x1f) << 6) | (byte2 & 0x3f)
@@ -258,6 +259,7 @@ export const toUTF8 = (arr: number[]): string => {
258259
const byte3 = arr[i + 2]
259260
if ((byte2 & 0xc0) !== 0x80 || (byte3 & 0xc0) !== 0x80) {
260261
emitReplacement()
262+
i += 2
261263
continue
262264
}
263265
const codePoint =
@@ -283,6 +285,7 @@ export const toUTF8 = (arr: number[]): string => {
283285
(byte4 & 0xc0) !== 0x80
284286
) {
285287
emitReplacement()
288+
i += 3
286289
continue
287290
}
288291
const codePoint =

0 commit comments

Comments
 (0)