diff --git a/src/auth/scram.ts b/src/auth/scram.ts index 39b505a..94eaca5 100644 --- a/src/auth/scram.ts +++ b/src/auth/scram.ts @@ -6,6 +6,7 @@ import { MongoDriverError } from "../error.ts"; import { b64, Binary, crypto as stdCrypto, Document, hex } from "../../deps.ts"; import { driverMetadata } from "../protocol/mod.ts"; import { pbkdf2 } from "./pbkdf2.ts"; +import { decodeBase64 } from "../utils/decode_base64.ts"; type CryptoMethod = "sha1" | "sha256"; @@ -160,7 +161,7 @@ export async function continueScramConversation( const withoutProof = `c=biws,r=${rnonce}`; const saltedPassword = await HI( processedPassword, - b64.decode(salt), + decodeBase64(salt), iterations, cryptoMethod, ); @@ -193,7 +194,7 @@ export async function continueScramConversation( ); if ( !compareDigest( - b64.decode(parsedResponse.v), + decodeBase64(parsedResponse.v), new Uint8Array(serverSignature), ) ) { diff --git a/src/utils/decode_base64.ts b/src/utils/decode_base64.ts new file mode 100644 index 0000000..ca702fe --- /dev/null +++ b/src/utils/decode_base64.ts @@ -0,0 +1,25 @@ +export function decodeBase64(b64: string): Uint8Array { + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let base64 = b64.replace(/-/g, "+").replace(/_/g, "/"); + + while (base64.length % 4) { + base64 += "="; + } + + let bitString = ""; + for (let i = 0; i < base64.length; i++) { + const char = base64.charAt(i); + if (char !== "=") { + const charIndex = chars.indexOf(char); + bitString += charIndex.toString(2).padStart(6, "0"); + } + } + + const bytes = new Uint8Array(bitString.length / 8); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(bitString.substring(8 * i, 8 * (i + 1)), 2); + } + + return bytes; +} diff --git a/tests/cases/11_decode_base64.ts b/tests/cases/11_decode_base64.ts new file mode 100644 index 0000000..4d679e0 --- /dev/null +++ b/tests/cases/11_decode_base64.ts @@ -0,0 +1,41 @@ +import { decodeBase64 } from "../../src/utils/decode_base64.ts"; + +import { assertEquals, describe, it } from "./../test.deps.ts"; + +describe("decodeBase64", () => { + it({ + name: "should correctly decode a standard base64 encoded string", + fn() { + const encoded = "SGVsbG8gV29ybGQ="; // "Hello World" in base64 + const decoded = decodeBase64(encoded); + assertEquals(new TextDecoder().decode(decoded), "Hello World"); + }, + }); + + it({ + name: "should correctly decode a URL-safe base64 encoded string", + fn() { + const encoded = "SGVsbG8tV29ybGRf"; // URL-safe base64 variant + const decoded = decodeBase64(encoded); + assertEquals(new TextDecoder().decode(decoded), "Hello-World_"); + }, + }); + + it({ + name: "should handle base64 strings with missing padding", + fn() { + const encoded = "SGVsbG8gV29ybGQ"; // Missing '=' at the end + const decoded = decodeBase64(encoded); + assertEquals(new TextDecoder().decode(decoded), "Hello World"); + }, + }); + + it({ + name: "should return an empty array for an empty string", + fn() { + const encoded = ""; + const decoded = decodeBase64(encoded); + assertEquals(decoded.length, 0); + }, + }); +});