Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hono",
"version": "4.11.9",
"version": "4.11.10",
"description": "Web framework built on Web Standards",
"main": "dist/cjs/index.js",
"type": "module",
Expand Down
29 changes: 21 additions & 8 deletions src/utils/buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@ describe('buffer', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(await timingSafeEqual(undefined, undefined)).toBe(true)
expect(await timingSafeEqual(true, true)).toBe(true)
expect(await timingSafeEqual(false, false)).toBe(true)
expect(
await timingSafeEqual(true, true, (d: boolean) =>
createHash('sha256').update(d.toString()).digest('hex')
)
)
})

it('negative', async () => {
Expand All @@ -58,10 +51,30 @@ describe('buffer', () => {
await timingSafeEqual('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'a')
).toBe(false)
expect(await timingSafeEqual('alpha', 'beta')).toBe(false)
expect(await timingSafeEqual(false, true)).toBe(false)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(await timingSafeEqual(false, undefined)).toBe(false)

expect(
await timingSafeEqual(
// well known md5 hash collision
// https://marc-stevens.nl/research/md5-1block-collision/
'TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak',
'TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak',
(input) => createHash('md5').update(input).digest('hex')
)
).toBe(false)
})

it.skip('comparing variables except string are deprecated', async () => {
expect(await timingSafeEqual(true, true)).toBe(true)
expect(await timingSafeEqual(false, false)).toBe(true)
expect(
await timingSafeEqual(true, true, (d: boolean) =>
createHash('sha256').update(d.toString()).digest('hex')
)
)
expect(await timingSafeEqual(false, true)).toBe(false)
expect(
await timingSafeEqual(
() => {},
Expand Down
61 changes: 56 additions & 5 deletions src/utils/buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,73 @@ export const equal = (a: ArrayBuffer, b: ArrayBuffer): boolean => {
return true
}

export const timingSafeEqual = async (
a: string | object | boolean,
b: string | object | boolean,
const constantTimeEqualString = (a: string, b: string): boolean => {
const aLen = a.length
const bLen = b.length
const maxLen = Math.max(aLen, bLen)
let out = aLen ^ bLen
for (let i = 0; i < maxLen; i++) {
const aChar = i < aLen ? a.charCodeAt(i) : 0
const bChar = i < bLen ? b.charCodeAt(i) : 0
out |= aChar ^ bChar
}
return out === 0
}

type StringHashFunction = (input: string) => string | null | Promise<string | null>

const timingSafeEqualString = async (
a: string,
b: string,
hashFunction?: StringHashFunction
): Promise<boolean> => {
if (!hashFunction) {
hashFunction = sha256
}

const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)])

if (sa == null || sb == null || typeof sa !== 'string' || typeof sb !== 'string') {
return false
}

const hashEqual = constantTimeEqualString(sa, sb)
const originalEqual = constantTimeEqualString(a, b)

return hashEqual && originalEqual
}

type TimingSafeEqual = {
(a: string, b: string, hashFunction?: StringHashFunction): Promise<boolean>
/**
* @deprecated object and boolean signatures that take boolean as first and second arguments, and functions with signatures that take non-string arguments have been deprecated
*/
(
a: string | object | boolean,
b: string | object | boolean,
hashFunction?: Function
): Promise<boolean>
}
export const timingSafeEqual: TimingSafeEqual = async (
a,
b,
hashFunction?: Function
): Promise<boolean> => {
if (typeof a === 'string' && typeof b === 'string') {
return timingSafeEqualString(a, b, hashFunction as StringHashFunction)
}

if (!hashFunction) {
hashFunction = sha256
}

const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)])

if (!sa || !sb) {
if (!sa || !sb || typeof sa !== 'string' || typeof sb !== 'string') {
return false
}

return sa === sb && a === b
return timingSafeEqualString(sa, sb)
}

export const bufferToString = (buffer: ArrayBuffer): string => {
Expand Down
Loading