Skip to content

Commit

Permalink
refactor: simplify signature compute and verify
Browse files Browse the repository at this point in the history
  • Loading branch information
Yizack committed Aug 24, 2024
1 parent f89c0bc commit 1d4d3f3
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 51 deletions.
28 changes: 28 additions & 0 deletions src/runtime/server/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
import { subtle } from 'node:crypto'
import { Buffer } from 'node:buffer'

export const encoder = new TextEncoder()
export const hmacAlgorithm = { name: 'HMAC', hash: 'SHA-256' }
export const ed25519Algorithm = { name: 'Ed25519', namedCurve: 'Ed25519' }

export const computeSignature = async (
secretKey: string,
algorithm: typeof hmacAlgorithm | typeof ed25519Algorithm,
payload: string,
options?: Partial<{ extractable: boolean, encoding: BufferEncoding }>,
) => {
const key = await subtle.importKey('raw', encoder.encode(secretKey), algorithm, options?.extractable ?? false, ['sign'])
const signature = await subtle.sign(algorithm.name, key, encoder.encode(payload))
return Buffer.from(signature).toString(options?.encoding ?? 'hex')
}

export const verifyPublicSignature = async (
publicKey: string,
algorithm: typeof hmacAlgorithm | typeof ed25519Algorithm,
payload: string,
signature: string,
options?: Partial<{ extractable: boolean, encoding: BufferEncoding }>,
) => {
const publicKeyBuffer = Buffer.from(publicKey, 'hex')
const webhookSignatureBuffer = Buffer.from(signature, 'hex')
const key = await subtle.importKey('raw', publicKeyBuffer, algorithm, options?.extractable ?? false, ['verify'])
const result = await subtle.verify(algorithm.name, key, webhookSignatureBuffer, encoder.encode(payload))
return result
}
10 changes: 2 additions & 8 deletions src/runtime/server/lib/validators/discord.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { subtle } from 'node:crypto'
import { Buffer } from 'node:buffer'
import { type H3Event, getRequestHeaders, readRawBody } from 'h3'
import { encoder, ed25519Algorithm } from '../helpers'
import { verifyPublicSignature, ed25519Algorithm } from '../helpers'
import { useRuntimeConfig } from '#imports'

const DISCORD_SIGNATURE = 'x-signature-ed25519'
Expand All @@ -24,11 +22,7 @@ export const isValidDiscordWebhook = async (event: H3Event): Promise<boolean> =>
if (!body || !webhookSignature || !webhookTimestamp) return false

const payloadWithTime = webhookTimestamp + body
const publicKeyBuffer = Buffer.from(publicKey, 'hex')
const webhookSignatureBuffer = Buffer.from(webhookSignature, 'hex')

const key = await subtle.importKey('raw', publicKeyBuffer, ed25519Algorithm, true, ['verify'])
const isValid = await subtle.verify(ed25519Algorithm.name, key, webhookSignatureBuffer, encoder.encode(payloadWithTime))

const isValid = await verifyPublicSignature(publicKey, ed25519Algorithm, payloadWithTime, webhookSignature)
return isValid
}
11 changes: 2 additions & 9 deletions src/runtime/server/lib/validators/github.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { subtle } from 'node:crypto'
import { Buffer } from 'node:buffer'
import { type H3Event, getRequestHeaders, readRawBody } from 'h3'
import { encoder, hmacAlgorithm } from '../helpers'
import { computeSignature, hmacAlgorithm } from '../helpers'
import { useRuntimeConfig } from '#imports'

const GITHUB_SIGNATURE = 'X-Hub-Signature-256'.toLowerCase()
Expand All @@ -24,11 +22,6 @@ export const isValidGithubWebhook = async (event: H3Event): Promise<boolean> =>
const parts = header.split('=')
const webhookSignature = parts[1]

const extractable = false
const key = await subtle.importKey('raw', encoder.encode(secretKey), hmacAlgorithm, extractable, ['sign'])
const hmac = await subtle.sign(hmacAlgorithm.name, key, encoder.encode(body))

const computedHash = Buffer.from(hmac).toString('hex')

const computedHash = await computeSignature(secretKey, hmacAlgorithm, body)
return computedHash === webhookSignature
}
12 changes: 3 additions & 9 deletions src/runtime/server/lib/validators/heroku.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { subtle } from 'node:crypto'
import { Buffer } from 'node:buffer'
import { type H3Event, getRequestHeaders, readRawBody } from 'h3'
import { encoder, hmacAlgorithm } from '../helpers'
import { computeSignature, hmacAlgorithm } from '../helpers'
import { useRuntimeConfig } from '#imports'

const HEROKU_HMAC = 'Heroku-Webhook-Hmac-SHA256'.toLowerCase()
Expand All @@ -23,10 +21,6 @@ export const isValidHerokuWebhook = async (event: H3Event): Promise<boolean> =>

const webhookSignature = header

const key = await subtle.importKey('raw', encoder.encode(secretKey), hmacAlgorithm, false, ['sign'])
const hmac = await subtle.sign(hmacAlgorithm.name, key, encoder.encode(body))

const computedBase64 = Buffer.from(hmac).toString('base64')

return computedBase64 === webhookSignature
const computedHash = await computeSignature(secretKey, hmacAlgorithm, body, { encoding: 'base64' })
return computedHash === webhookSignature
}
10 changes: 2 additions & 8 deletions src/runtime/server/lib/validators/paddle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { subtle } from 'node:crypto'
import { Buffer } from 'node:buffer'
import { type H3Event, getRequestHeaders, readRawBody } from 'h3'
import { encoder, hmacAlgorithm } from '../helpers'
import { computeSignature, hmacAlgorithm } from '../helpers'
import { useRuntimeConfig } from '#imports'

const MAX_VALID_TIME_DIFFERENCE = 5
Expand Down Expand Up @@ -45,10 +43,6 @@ export const isValidPaddleWebhook = async (event: H3Event): Promise<boolean> =>

const payloadWithTime = `${webhookTimestamp}:${body}`

const key = await subtle.importKey('raw', encoder.encode(webhookId), hmacAlgorithm, false, ['sign'])
const hmac = await subtle.sign(hmacAlgorithm.name, key, encoder.encode(payloadWithTime))

const computedHash = Buffer.from(hmac).toString('hex')

const computedHash = await computeSignature(webhookId, hmacAlgorithm, payloadWithTime)
return computedHash === webhookSignature
}
10 changes: 2 additions & 8 deletions src/runtime/server/lib/validators/stripe.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { subtle } from 'node:crypto'
import { Buffer } from 'node:buffer'
import { type H3Event, getRequestHeaders, readRawBody } from 'h3'
import { encoder, hmacAlgorithm } from '../helpers'
import { computeSignature, hmacAlgorithm } from '../helpers'
import { useRuntimeConfig } from '#imports'

const DEFAULT_TOLERANCE = 300
Expand Down Expand Up @@ -45,10 +43,6 @@ export const isValidStripeWebhook = async (event: H3Event): Promise<boolean> =>

const payloadWithTime = `${webhookTimestamp}.${body}`

const key = await subtle.importKey('raw', encoder.encode(secretKey), hmacAlgorithm, false, ['sign'])
const hmac = await subtle.sign(hmacAlgorithm.name, key, encoder.encode(payloadWithTime))

const computedHash = Buffer.from(hmac).toString('hex')

const computedHash = await computeSignature(secretKey, hmacAlgorithm, payloadWithTime)
return computedHash === webhookSignature
}
12 changes: 3 additions & 9 deletions src/runtime/server/lib/validators/twitch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { subtle } from 'node:crypto'
import { Buffer } from 'node:buffer'
import { type H3Event, getRequestHeaders, readRawBody } from 'h3'
import { encoder, hmacAlgorithm } from '../helpers'
import { computeSignature, hmacAlgorithm } from '../helpers'
import { useRuntimeConfig } from '#imports'

const TWITCH_MESSAGE_ID = 'Twitch-Eventsub-Message-Id'.toLowerCase()
Expand All @@ -28,10 +26,6 @@ export const isValidTwitchWebhook = async (event: H3Event): Promise<boolean> =>

const message = message_id + message_timestamp + body

const key = await subtle.importKey('raw', encoder.encode(secretKey), hmacAlgorithm, false, ['sign'])
const hmac = await subtle.sign(hmacAlgorithm.name, key, encoder.encode(message))

const computedHash = HMAC_PREFIX + Buffer.from(hmac).toString('hex')

return computedHash === message_signature
const computedHash = await computeSignature(secretKey, hmacAlgorithm, message)
return HMAC_PREFIX + computedHash === message_signature
}

0 comments on commit 1d4d3f3

Please sign in to comment.