Add support for CPE digest authentication
This commit is contained in:
1 parent
f21ce3053a
commit
344ed3fdee
5 files changed
+228
-86
No files matched your search
@@ -27,6 +27,7 @@ const externals = [
|
|||||||
"fs",
|
"fs",
|
||||||
"cluster",
|
"cluster",
|
||||||
"os",
|
"os",
|
||||||
|
"tls",
|
||||||
"http",
|
"http",
|
||||||
"https",
|
"https",
|
||||||
"zlib",
|
"zlib",
|
||||||
|
|||||||
+81
-45
@@ -19,31 +19,9 @@
|
|||||||
|
|
||||||
import { createHash, randomBytes, pbkdf2 } from "crypto";
|
import { createHash, randomBytes, pbkdf2 } from "crypto";
|
||||||
|
|
||||||
export function parseAuthorizationHeader(authHeader): { method: string } {
|
function parseHeaderFeilds(str: string): {} {
|
||||||
authHeader = authHeader.trim();
|
const res = {};
|
||||||
const method = authHeader.split(" ", 1)[0];
|
const parts = str.split(",");
|
||||||
const res = { method: method };
|
|
||||||
|
|
||||||
if (method === "Basic") {
|
|
||||||
// Inspired by https://github.com/jshttp/basic-auth
|
|
||||||
const USER_PASS_REGEX = /^([^:]*):(.*)$/;
|
|
||||||
const creds = USER_PASS_REGEX.exec(
|
|
||||||
Buffer.from(authHeader.slice(method.length + 1), "base64").toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!creds) throw new Error("Unable to parse auth header");
|
|
||||||
res["username"] = creds[1];
|
|
||||||
res["password"] = creds[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseWwwAuthenticateHeader(authHeader): {} {
|
|
||||||
authHeader = authHeader.trim();
|
|
||||||
const method = authHeader.split(" ", 1)[0];
|
|
||||||
const res = { method: method };
|
|
||||||
const parts = authHeader.slice(method.length + 1).split(",");
|
|
||||||
|
|
||||||
let part;
|
let part;
|
||||||
while ((part = parts.shift()) != null) {
|
while ((part = parts.shift()) != null) {
|
||||||
@@ -72,33 +50,57 @@ export function parseWwwAuthenticateHeader(authHeader): {} {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseAuthorizationHeader(authHeader): { method: string } {
|
||||||
|
authHeader = authHeader.trim();
|
||||||
|
const method = authHeader.split(" ", 1)[0];
|
||||||
|
const res = { method: method };
|
||||||
|
|
||||||
|
if (method === "Basic") {
|
||||||
|
// Inspired by https://github.com/jshttp/basic-auth
|
||||||
|
const USER_PASS_REGEX = /^([^:]*):(.*)$/;
|
||||||
|
const creds = USER_PASS_REGEX.exec(
|
||||||
|
Buffer.from(authHeader.slice(method.length + 1), "base64").toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!creds) throw new Error("Unable to parse auth header");
|
||||||
|
res["username"] = creds[1];
|
||||||
|
res["password"] = creds[2];
|
||||||
|
} else if (method === "Digest") {
|
||||||
|
Object.assign(res, parseHeaderFeilds(authHeader.slice(method.length + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseWwwAuthenticateHeader(authHeader): {} {
|
||||||
|
authHeader = authHeader.trim();
|
||||||
|
const method = authHeader.split(" ", 1)[0];
|
||||||
|
const res = { method: method };
|
||||||
|
Object.assign(res, parseHeaderFeilds(authHeader.slice(method.length + 1)));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
export function basic(username, password): string {
|
export function basic(username, password): string {
|
||||||
return "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
return "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function digest(
|
export function digest(
|
||||||
username,
|
username: string | Buffer,
|
||||||
password,
|
realm: string | Buffer,
|
||||||
uri,
|
password: string | Buffer,
|
||||||
httpMethod,
|
nonce: string | Buffer,
|
||||||
body,
|
httpMethod: string | Buffer,
|
||||||
authHeader
|
uri: string | Buffer,
|
||||||
|
qop?: string | Buffer,
|
||||||
|
body?: string | Buffer,
|
||||||
|
cnonce?: string | Buffer,
|
||||||
|
nc?: string | Buffer
|
||||||
): string {
|
): string {
|
||||||
const cnonce = "0a4f113b";
|
|
||||||
const nc = "00000001";
|
|
||||||
let qop;
|
|
||||||
|
|
||||||
if (authHeader.qop) {
|
|
||||||
if (authHeader.qop.indexOf(",") !== -1) qop = "auth";
|
|
||||||
// Either auth or auth-int, prefer auth
|
|
||||||
else qop = authHeader.qop;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ha1 = createHash("md5");
|
const ha1 = createHash("md5");
|
||||||
ha1
|
ha1
|
||||||
.update(username)
|
.update(username)
|
||||||
.update(":")
|
.update(":")
|
||||||
.update(authHeader.realm)
|
.update(realm)
|
||||||
.update(":")
|
.update(":")
|
||||||
.update(password);
|
.update(password);
|
||||||
// TODO support "MD5-sess" algorithm directive
|
// TODO support "MD5-sess" algorithm directive
|
||||||
@@ -112,7 +114,7 @@ export function digest(
|
|||||||
|
|
||||||
if (qop === "auth-int") {
|
if (qop === "auth-int") {
|
||||||
const bodyHash = createHash("md5")
|
const bodyHash = createHash("md5")
|
||||||
.update(body)
|
.update(body || "")
|
||||||
.digest("hex");
|
.digest("hex");
|
||||||
ha2.update(":").update(bodyHash);
|
ha2.update(":").update(bodyHash);
|
||||||
}
|
}
|
||||||
@@ -123,7 +125,7 @@ export function digest(
|
|||||||
hash
|
hash
|
||||||
.update(ha1d)
|
.update(ha1d)
|
||||||
.update(":")
|
.update(":")
|
||||||
.update(authHeader.nonce);
|
.update(nonce);
|
||||||
if (qop) {
|
if (qop) {
|
||||||
hash
|
hash
|
||||||
.update(":")
|
.update(":")
|
||||||
@@ -135,13 +137,47 @@ export function digest(
|
|||||||
}
|
}
|
||||||
hash.update(":").update(ha2d);
|
hash.update(":").update(ha2d);
|
||||||
|
|
||||||
|
return hash.digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function solveDigest(
|
||||||
|
username: string | Buffer,
|
||||||
|
password: string | Buffer,
|
||||||
|
uri: string | Buffer,
|
||||||
|
httpMethod: string | Buffer,
|
||||||
|
body: string | Buffer,
|
||||||
|
authHeader
|
||||||
|
): string {
|
||||||
|
const cnonce = randomBytes(8).toString("hex");
|
||||||
|
const nc = "00000001";
|
||||||
|
|
||||||
|
let qop;
|
||||||
|
if (authHeader.qop) {
|
||||||
|
if (authHeader.qop.indexOf(",") !== -1) qop = "auth";
|
||||||
|
// Either auth or auth-int, prefer auth
|
||||||
|
else qop = authHeader.qop;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = digest(
|
||||||
|
username,
|
||||||
|
authHeader.realm,
|
||||||
|
password,
|
||||||
|
authHeader.nonce,
|
||||||
|
httpMethod,
|
||||||
|
uri,
|
||||||
|
qop,
|
||||||
|
body,
|
||||||
|
cnonce,
|
||||||
|
nc
|
||||||
|
);
|
||||||
|
|
||||||
let authString = `Digest username="${username}"`;
|
let authString = `Digest username="${username}"`;
|
||||||
authString += `,realm="${authHeader.realm}"`;
|
authString += `,realm="${authHeader.realm}"`;
|
||||||
authString += `,nonce="${authHeader.nonce}"`;
|
authString += `,nonce="${authHeader.nonce}"`;
|
||||||
authString += `,uri="${uri}"`;
|
authString += `,uri="${uri}"`;
|
||||||
if (authHeader.algorithm) authString += `,algorithm=${authHeader.algorithm}`;
|
if (authHeader.algorithm) authString += `,algorithm=${authHeader.algorithm}`;
|
||||||
if (qop) authString += `,qop=${qop},nc=${nc},cnonce="${cnonce}"`;
|
if (qop) authString += `,qop=${qop},nc=${nc},cnonce="${cnonce}"`;
|
||||||
authString += `,response="${hash.digest("hex")}"`;
|
authString += `,response="${hash}"`;
|
||||||
if (authHeader.opaque) authString += `,opaque="${authHeader.opaque}"`;
|
if (authHeader.opaque) authString += `,opaque="${authHeader.opaque}"`;
|
||||||
|
|
||||||
return authString;
|
return authString;
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export async function httpConnectionRequest(
|
|||||||
opts = Object.assign(
|
opts = Object.assign(
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: auth.digest(
|
Authorization: auth.solveDigest(
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
options.path,
|
options.path,
|
||||||
|
|||||||
+96
-40
@@ -49,14 +49,17 @@ import {
|
|||||||
import { IncomingMessage, ServerResponse } from "http";
|
import { IncomingMessage, ServerResponse } from "http";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
import { TLSSocket } from "tls";
|
||||||
|
|
||||||
const gzipPromisified = promisify(zlib.gzip);
|
const gzipPromisified = promisify(zlib.gzip);
|
||||||
const deflatePromisified = promisify(zlib.deflate);
|
const deflatePromisified = promisify(zlib.deflate);
|
||||||
|
|
||||||
|
const REALM = "GenieACS";
|
||||||
const MAX_CYCLES = 4;
|
const MAX_CYCLES = 4;
|
||||||
const MAX_CONCURRENT_REQUESTS = +config.get("MAX_CONCURRENT_REQUESTS");
|
const MAX_CONCURRENT_REQUESTS = +config.get("MAX_CONCURRENT_REQUESTS");
|
||||||
|
|
||||||
const currentSessions = new WeakMap<Socket, SessionContext>();
|
const currentSessions = new WeakMap<Socket, SessionContext>();
|
||||||
|
const sessionsNonces = new WeakMap<Socket, string>();
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
concurrentRequests: 0,
|
concurrentRequests: 0,
|
||||||
@@ -65,7 +68,10 @@ const stats = {
|
|||||||
initiatedSessions: 0
|
initiatedSessions: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
async function authenticate(sessionContext: SessionContext): Promise<boolean> {
|
async function authenticate(
|
||||||
|
sessionContext: SessionContext,
|
||||||
|
body: string | Buffer
|
||||||
|
): Promise<boolean> {
|
||||||
const authExpression: Expression = localCache.getConfigExpression(
|
const authExpression: Expression = localCache.getConfigExpression(
|
||||||
sessionContext.cacheSnapshot,
|
sessionContext.cacheSnapshot,
|
||||||
"cwmp.auth"
|
"cwmp.auth"
|
||||||
@@ -80,6 +86,21 @@ async function authenticate(sessionContext: SessionContext): Promise<boolean> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authentication && authentication.method === "Digest") {
|
||||||
|
const sessionNonce = sessionsNonces.get(
|
||||||
|
sessionContext.httpRequest.connection
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!sessionNonce ||
|
||||||
|
authentication.nonce !== sessionNonce ||
|
||||||
|
(authentication.qop && (!authentication.cnonce || !authentication.nc))
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
authentication["body"] = body;
|
||||||
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const res = await evaluateAsync(
|
const res = await evaluateAsync(
|
||||||
authExpression,
|
authExpression,
|
||||||
@@ -96,14 +117,33 @@ async function authenticate(sessionContext: SessionContext): Promise<boolean> {
|
|||||||
const { fault, value } = await extensions.run(e.slice(2));
|
const { fault, value } = await extensions.run(e.slice(2));
|
||||||
return fault ? null : value;
|
return fault ? null : value;
|
||||||
} else if (e[1] === "AUTH") {
|
} else if (e[1] === "AUTH") {
|
||||||
if (authentication && authentication["method"] === "Basic") {
|
const username = e[2];
|
||||||
return (
|
const password = e[3];
|
||||||
authentication["username"] === e[2] &&
|
if (username != null && password != null && authentication) {
|
||||||
authentication["password"] === e[3]
|
if (authentication["method"] === "Basic") {
|
||||||
);
|
return (
|
||||||
} else {
|
authentication["username"] === e[2] &&
|
||||||
return false;
|
authentication["password"] === e[3]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authentication["method"] === "Digest") {
|
||||||
|
const expected = auth.digest(
|
||||||
|
username,
|
||||||
|
REALM,
|
||||||
|
password,
|
||||||
|
authentication["nonce"],
|
||||||
|
"POST",
|
||||||
|
authentication["uri"],
|
||||||
|
authentication["qop"],
|
||||||
|
authentication["body"],
|
||||||
|
authentication["cnonce"],
|
||||||
|
authentication["nc"]
|
||||||
|
);
|
||||||
|
return expected === authentication["response"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
@@ -898,7 +938,7 @@ async function cacheDueTasksAndFaultsAndOperations(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportBadState(sessionContext: SessionContext): void {
|
async function reportBadState(sessionContext: SessionContext): Promise<void> {
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
message: "Bad session state",
|
message: "Bad session state",
|
||||||
sessionContext: sessionContext
|
sessionContext: sessionContext
|
||||||
@@ -908,14 +948,45 @@ function reportBadState(sessionContext: SessionContext): void {
|
|||||||
sessionContext.httpResponse.end("Bad session state");
|
sessionContext.httpResponse.end("Bad session state");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function responseUnauthorized(
|
||||||
|
sessionContext: SessionContext
|
||||||
|
): Promise<void> {
|
||||||
|
const authHeader = sessionContext.httpRequest.headers["authorization"];
|
||||||
|
const resHeaders = {};
|
||||||
|
if (authHeader) {
|
||||||
|
// Invalid credentials
|
||||||
|
logger.accessError({
|
||||||
|
message: "Authentication failure",
|
||||||
|
sessionContext: sessionContext
|
||||||
|
});
|
||||||
|
|
||||||
|
resHeaders["Connection"] = "close";
|
||||||
|
} else {
|
||||||
|
if (sessionContext.httpRequest.socket instanceof TLSSocket) {
|
||||||
|
resHeaders["WWW-Authenticate"] = `Basic realm="${REALM}"`;
|
||||||
|
} else {
|
||||||
|
const nonce = crypto.randomBytes(16).toString("hex");
|
||||||
|
sessionsNonces.set(sessionContext.httpRequest.connection, nonce);
|
||||||
|
let d = `Digest realm="${REALM}"`;
|
||||||
|
d += ',qop="auth,auth-int"';
|
||||||
|
d += `,nonce="${nonce}"`;
|
||||||
|
|
||||||
|
resHeaders["WWW-Authenticate"] = d;
|
||||||
|
}
|
||||||
|
currentSessions.set(sessionContext.httpRequest.connection, sessionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionContext.httpResponse.writeHead(401, resHeaders);
|
||||||
|
sessionContext.httpResponse.end("Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
async function processRequest(
|
async function processRequest(
|
||||||
sessionContext: SessionContext,
|
sessionContext: SessionContext,
|
||||||
rpc: SoapMessage
|
rpc: SoapMessage
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (rpc.cpeRequest) {
|
if (rpc.cpeRequest) {
|
||||||
if (rpc.cpeRequest.name === "Inform") {
|
if (rpc.cpeRequest.name === "Inform") {
|
||||||
if (sessionContext.state !== 0)
|
if (sessionContext.state !== 0) return reportBadState(sessionContext);
|
||||||
return void reportBadState(sessionContext);
|
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
@@ -924,8 +995,7 @@ async function processRequest(
|
|||||||
});
|
});
|
||||||
return inform(sessionContext, rpc);
|
return inform(sessionContext, rpc);
|
||||||
} else if (rpc.cpeRequest.name === "TransferComplete") {
|
} else if (rpc.cpeRequest.name === "TransferComplete") {
|
||||||
if (sessionContext.state !== 1)
|
if (sessionContext.state !== 1) return reportBadState(sessionContext);
|
||||||
return void reportBadState(sessionContext);
|
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
@@ -957,12 +1027,12 @@ async function processRequest(
|
|||||||
throw new Error("ACS method not supported");
|
throw new Error("ACS method not supported");
|
||||||
}
|
}
|
||||||
} else if (rpc.cpeResponse) {
|
} else if (rpc.cpeResponse) {
|
||||||
if (sessionContext.state !== 2) return void reportBadState(sessionContext);
|
if (sessionContext.state !== 2) return reportBadState(sessionContext);
|
||||||
|
|
||||||
await session.rpcResponse(sessionContext, rpc.id, rpc.cpeResponse);
|
await session.rpcResponse(sessionContext, rpc.id, rpc.cpeResponse);
|
||||||
return nextRpc(sessionContext);
|
return nextRpc(sessionContext);
|
||||||
} else if (rpc.cpeFault) {
|
} else if (rpc.cpeFault) {
|
||||||
if (sessionContext.state !== 2) return void reportBadState(sessionContext);
|
if (sessionContext.state !== 2) return reportBadState(sessionContext);
|
||||||
|
|
||||||
logger.accessWarn({
|
logger.accessWarn({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
@@ -978,7 +1048,7 @@ async function processRequest(
|
|||||||
return nextRpc(sessionContext);
|
return nextRpc(sessionContext);
|
||||||
} else {
|
} else {
|
||||||
// CPE sent empty response
|
// CPE sent empty response
|
||||||
if (sessionContext.state !== 1) return void reportBadState(sessionContext);
|
if (sessionContext.state !== 1) return reportBadState(sessionContext);
|
||||||
|
|
||||||
sessionContext.state = 2;
|
sessionContext.state = 2;
|
||||||
const { faults, operations } = await session.timeoutOperations(
|
const { faults, operations } = await session.timeoutOperations(
|
||||||
@@ -1121,13 +1191,15 @@ async function listenerAsync(
|
|||||||
return processRequest(sessionContext, rpc);
|
return processRequest(sessionContext, rpc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newConnection = !currentSessions.has(httpRequest.connection);
|
||||||
|
|
||||||
const sessionContext = await getSession(httpRequest.connection, sessionId);
|
const sessionContext = await getSession(httpRequest.connection, sessionId);
|
||||||
|
|
||||||
if (sessionContext) {
|
if (sessionContext) {
|
||||||
sessionContext.httpRequest = httpRequest;
|
sessionContext.httpRequest = httpRequest;
|
||||||
sessionContext.httpResponse = httpResponse;
|
sessionContext.httpResponse = httpResponse;
|
||||||
if (
|
if (
|
||||||
sessionContext.sessionId !== sessionId ||
|
(newConnection && sessionContext.sessionId !== sessionId) ||
|
||||||
sessionContext.lastActivity + sessionContext.timeout * 1000 < Date.now()
|
sessionContext.lastActivity + sessionContext.timeout * 1000 < Date.now()
|
||||||
) {
|
) {
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
@@ -1171,20 +1243,14 @@ async function listenerAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sessionContext) {
|
if (sessionContext) {
|
||||||
|
// Authenticate in case of new connection or unauthenticated session
|
||||||
const authenticated =
|
const authenticated =
|
||||||
sessionContext.state === 0 ? await authenticate(sessionContext) : true;
|
sessionContext.state === 0 || newConnection
|
||||||
|
? await authenticate(sessionContext, body)
|
||||||
|
: true;
|
||||||
|
|
||||||
if (!authenticated) {
|
if (!authenticated) return responseUnauthorized(sessionContext);
|
||||||
logger.accessError({
|
|
||||||
message: "Authentication failure",
|
|
||||||
sessionContext: sessionContext
|
|
||||||
});
|
|
||||||
|
|
||||||
httpResponse.writeHead(401);
|
|
||||||
httpResponse.end("Unauthorized");
|
|
||||||
currentSessions.set(httpRequest.connection, sessionContext);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return parsedRpc(sessionContext, rpc, parseWarnings);
|
return parsedRpc(sessionContext, rpc, parseWarnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1262,18 +1328,8 @@ async function listenerAsync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const authenticated = await authenticate(_sessionContext);
|
const authenticated = await authenticate(_sessionContext, body);
|
||||||
if (!authenticated) {
|
if (!authenticated) return responseUnauthorized(_sessionContext);
|
||||||
logger.accessError({
|
|
||||||
message: "Authentication failure",
|
|
||||||
sessionContext: _sessionContext
|
|
||||||
});
|
|
||||||
|
|
||||||
httpResponse.writeHead(401);
|
|
||||||
httpResponse.end("Unauthorized");
|
|
||||||
currentSessions.set(httpRequest.connection, _sessionContext);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedRpc(_sessionContext, rpc, parseWarnings);
|
parsedRpc(_sessionContext, rpc, parseWarnings);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import ava from "ava";
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
|
import * as auth from "../lib/auth";
|
||||||
|
|
||||||
|
ava("digest", t => {
|
||||||
|
const username = "test";
|
||||||
|
const password = "test";
|
||||||
|
const uri = "/";
|
||||||
|
const method = "POST";
|
||||||
|
const realm = "GeniceACS";
|
||||||
|
const nonce = randomBytes(16).toString("hex");
|
||||||
|
const body = randomBytes(128).toString();
|
||||||
|
|
||||||
|
const challenges = [
|
||||||
|
`Digest realm="${realm}",nonce="${nonce}"`,
|
||||||
|
`Digest realm="${realm}",nonce="${nonce}",qop="auth"`,
|
||||||
|
`Digest realm="${realm}",nonce="${nonce}",qop="auth-int"`
|
||||||
|
];
|
||||||
|
|
||||||
|
t.plan(challenges.length);
|
||||||
|
|
||||||
|
for (const challenge of challenges) {
|
||||||
|
const wwwAuthHeader = auth.parseWwwAuthenticateHeader(challenge);
|
||||||
|
const solution = auth.solveDigest(
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
uri,
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
wwwAuthHeader
|
||||||
|
);
|
||||||
|
const authHeader = auth.parseAuthorizationHeader(solution);
|
||||||
|
t.is(
|
||||||
|
authHeader["response"],
|
||||||
|
auth.digest(
|
||||||
|
username,
|
||||||
|
realm,
|
||||||
|
password,
|
||||||
|
nonce,
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
authHeader["qop"],
|
||||||
|
body,
|
||||||
|
authHeader["cnonce"],
|
||||||
|
authHeader["nc"]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user