Skip to content

Commit 314d8c9

Browse files
committed
polynomial time validateBase64String
1 parent bdecb62 commit 314d8c9

File tree

1 file changed

+44
-8
lines changed

1 file changed

+44
-8
lines changed

src/wallet/validationHelpers.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -266,19 +266,55 @@ function validateOptionalBase64String (
266266
* @throws WERR_INVALID_PARAMETER when invalid
267267
*/
268268
function validateBase64String (s: string, name: string, min?: number, max?: number): string {
269-
// Remove any whitespace and check if the string length is valid for Base64
270269
s = s.trim()
271-
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
272-
const paddingMatch = /=+$/.exec(s)
273-
const paddingCount = (paddingMatch != null) ? paddingMatch[0].length : 0
270+
if (s.length === 0) {
271+
throw new WERR_INVALID_PARAMETER(name, 'valid base64 string')
272+
}
274273

275-
if (paddingCount > 2 || (s.length % 4 !== 0 && paddingCount !== 0) || !base64Regex.test(s)) {
274+
let paddingCount = 0
275+
let validCharCount = 0
276+
277+
for (let i = 0; i < s.length; i++) {
278+
const char = s.charCodeAt(i)
279+
if (char >= 65 && char <= 90) continue // A-Z
280+
if (char >= 97 && char <= 122) continue // a-z
281+
if (char >= 48 && char <= 57) continue // 0-9
282+
if (char === 43) continue // +
283+
if (char === 47) continue // /
284+
if (char === 61) { // =
285+
if (i < s.length - 2) {
286+
throw new WERR_INVALID_PARAMETER(name, 'valid base64 string')
287+
}
288+
paddingCount++
289+
continue
290+
}
276291
throw new WERR_INVALID_PARAMETER(name, 'valid base64 string')
277292
}
278293

279-
const bytes = Utils.toArray(s, 'base64').length
280-
if (min !== undefined && bytes < min) throw new WERR_INVALID_PARAMETER(name, `at least ${min} length.`)
281-
if (max !== undefined && bytes > max) throw new WERR_INVALID_PARAMETER(name, `no more than ${max} length.`)
294+
// Padding rules
295+
if (paddingCount > 2) {
296+
throw new WERR_INVALID_PARAMETER(name, 'valid base64 string')
297+
}
298+
if (paddingCount > 0 && s.length % 4 !== 0) {
299+
throw new WERR_INVALID_PARAMETER(name, 'valid base64 string')
300+
}
301+
302+
// Length must be multiple of 4 if no padding, or valid with padding
303+
const mod = s.length % 4
304+
if (mod !== 0 && mod !== (4 - paddingCount)) {
305+
throw new WERR_INVALID_PARAMETER(name, 'valid base64 string')
306+
}
307+
308+
// Calculate decoded byte length: (valid chars * 6) / 8
309+
const encodedLength = s.length - paddingCount
310+
const bytes = Math.floor(encodedLength * 3 / 4)
311+
312+
if (min !== undefined && bytes < min) {
313+
throw new WERR_INVALID_PARAMETER(name, `at least ${min} bytes`)
314+
}
315+
if (max !== undefined && bytes > max) {
316+
throw new WERR_INVALID_PARAMETER(name, `no more than ${max} bytes`)
317+
}
282318

283319
return s
284320
}

0 commit comments

Comments
 (0)