Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/bases/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,14 @@ function createAlphabetIdx (alphabet: string, caseInsensitive: boolean): Record<
// Build the character lookup table:
const alphabetIdx: Record<string, number> = {}
for (let i = 0; i < alphabet.length; ++i) {
// The trailing '=' of a padded alphabet is a padding marker, not a data
// symbol. Skip it so an interior '=' has no index and is rejected as a
// non-base character in decode, instead of being folded into the output as
// bogus data. Trailing padding is stripped by decode before the lookup, so
// valid input round-trips unchanged.
if (alphabet[i] === '=') {
continue
}
alphabetIdx[alphabet[i]] = i
// For case-insensitive codecs, map the opposite case to the same index so
// differently cased input decodes without errors (multibase spec).
Expand Down
14 changes: 14 additions & 0 deletions test/test-multibase.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ describe('multibase', () => {
assert.throws(() => base64.decode(b64.substring(0, b64.length - 1)), 'Unexpected end of data')
})

it('rejects interior padding (RFC 4648 section 3.2)', () => {
const padded = { ...b32, ...b64 }
for (const base of [padded.base64pad, padded.base64urlpad, padded.base32pad, padded.base32hexpad]) {
const value = Uint8Array.from([0x41, 0x42])
// A valid encoding, with legitimate trailing padding, still round-trips.
const encoded = base.encode(value)
assert.deepStrictEqual(base.decode(encoded), value)
// A '=' spliced into the data run (padding may only be a trailing run) is
// invalid and must be rejected, not silently decoded to bogus bytes.
const tampered = encoded.slice(0, 2) + '=' + encoded.slice(2)
assert.throws(() => base.decode(tampered), `Non-${base.name} character`)
}
})

it('infers prefix and name correctly', () => {
const name = base32.name

Expand Down
Loading