Skip to content

Commit

Permalink
0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ovx committed Apr 27, 2024
1 parent 1da8379 commit cbc8624
Show file tree
Hide file tree
Showing 23 changed files with 457 additions and 105 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ Parameters:

Returns: `{ controller: AbortController, promise: Promise<Solution | null> }`

### `verifyServerSignature(payload, hmacKey)`

Verifies the server signature returned by the API. The payload can be a Base64-encoded JSON payload or an object.

Parameters:

- `payload: string | ServerSignaturePayload`
- `hmacKey: string`

Returns: `Promise<{ verificationData: ServerSignatureVerificationData | null, verified: boolean }>`

### `solveChallengeWorkers(workerScript, concurrency, challenge, salt, algorithm?, max?, start?)`

Finds a solution to the given challenge with [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) running concurrently.
Expand Down
6 changes: 4 additions & 2 deletions cjs/dist/helpers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import './crypto.js';
import type { Algorithm } from './types.js';
export declare const encoder: TextEncoder;
export declare function ab2hex(ab: ArrayBuffer | Uint8Array): string;
export declare function hash(algorithm: Algorithm, str: string): Promise<string>;
export declare function hmac(algorithm: Algorithm, str: string, secret: string): Promise<string>;
export declare function hash(algorithm: Algorithm, data: ArrayBuffer | string): Promise<ArrayBuffer>;
export declare function hashHex(algorithm: Algorithm, data: ArrayBuffer | string): Promise<string>;
export declare function hmac(algorithm: Algorithm, data: ArrayBuffer | string, secret: string): Promise<ArrayBuffer>;
export declare function hmacHex(algorithm: Algorithm, data: ArrayBuffer | string, secret: string): Promise<string>;
export declare function randomBytes(length: number): Uint8Array;
export declare function randomInt(max: number): number;
18 changes: 13 additions & 5 deletions cjs/dist/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.randomInt = exports.randomBytes = exports.hmac = exports.hash = exports.ab2hex = exports.encoder = void 0;
exports.randomInt = exports.randomBytes = exports.hmacHex = exports.hmac = exports.hashHex = exports.hash = exports.ab2hex = exports.encoder = void 0;
// @denoify-line-ignore
require("./crypto.js");
exports.encoder = new TextEncoder();
Expand All @@ -10,18 +10,26 @@ function ab2hex(ab) {
.join('');
}
exports.ab2hex = ab2hex;
async function hash(algorithm, str) {
return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), exports.encoder.encode(str)));
async function hash(algorithm, data) {
return crypto.subtle.digest(algorithm.toUpperCase(), typeof data === 'string' ? exports.encoder.encode(data) : new Uint8Array(data));
}
exports.hash = hash;
async function hmac(algorithm, str, secret) {
async function hashHex(algorithm, data) {
return ab2hex(await hash(algorithm, data));
}
exports.hashHex = hashHex;
async function hmac(algorithm, data, secret) {
const key = await crypto.subtle.importKey('raw', exports.encoder.encode(secret), {
name: 'HMAC',
hash: algorithm,
}, false, ['sign', 'verify']);
return ab2hex(await crypto.subtle.sign('HMAC', key, exports.encoder.encode(str)));
return crypto.subtle.sign('HMAC', key, typeof data === 'string' ? exports.encoder.encode(data) : new Uint8Array(data));
}
exports.hmac = hmac;
async function hmacHex(algorithm, data, secret) {
return ab2hex(await hmac(algorithm, data, secret));
}
exports.hmacHex = hmacHex;
function randomBytes(length) {
const ab = new Uint8Array(length);
crypto.getRandomValues(ab);
Expand Down
9 changes: 7 additions & 2 deletions cjs/dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import type { Challenge, ChallengeOptions, Payload, Solution } from './types.js';
import type { Challenge, ChallengeOptions, Payload, ServerSignaturePayload, ServerSignatureVerificationData, Solution } from './types.js';
export declare function createChallenge(options: ChallengeOptions): Promise<Challenge>;
export declare function verifySolution(payload: string | Payload, hmacKey: string): Promise<boolean>;
export declare function verifyServerSignature(payload: string | ServerSignaturePayload, hmacKey: string): Promise<{
verificationData: ServerSignatureVerificationData | null;
verified: boolean | null;
}>;
export declare function solveChallenge(challenge: string, salt: string, algorithm?: string, max?: number, start?: number): {
promise: Promise<Solution | null>;
controller: AbortController;
};
export declare function solveChallengeWorkers(workerScript: string | URL | (() => Worker), concurrency: number, challenge: string, salt: string, algorithm?: string, max?: number, startNumber?: number): Promise<Solution | null>;
declare const _default: {
createChallenge: typeof createChallenge;
verifySolution: typeof verifySolution;
solveChallenge: typeof solveChallenge;
solveChallengeWorkers: typeof solveChallengeWorkers;
verifyServerSignature: typeof verifyServerSignature;
verifySolution: typeof verifySolution;
};
export default _default;
53 changes: 42 additions & 11 deletions cjs/dist/index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.solveChallengeWorkers = exports.solveChallenge = exports.verifySolution = exports.createChallenge = void 0;
exports.solveChallengeWorkers = exports.solveChallenge = exports.verifyServerSignature = exports.verifySolution = exports.createChallenge = void 0;
const helpers_js_1 = require("./helpers.js");
const DEFAULT_MAX_NUMBER = 1e6;
const DEFAULT_SALT_LEN = 12;
const DEFAULT_ALG = 'SHA-256';
async function createChallenge(options) {
const algorithm = options.algorithm || DEFAULT_ALG;
const max = options.maxNumber || DEFAULT_MAX_NUMBER;
const maxnumber = options.maxnumber || options.maxNumber || DEFAULT_MAX_NUMBER;
const saltLength = options.saltLength || DEFAULT_SALT_LEN;
const salt = options.salt || (0, helpers_js_1.ab2hex)((0, helpers_js_1.randomBytes)(saltLength));
const number = options.number === void 0 ? (0, helpers_js_1.randomInt)(max) : options.number;
const challenge = await (0, helpers_js_1.hash)(algorithm, salt + number);
const number = options.number === void 0 ? (0, helpers_js_1.randomInt)(maxnumber) : options.number;
const challenge = await (0, helpers_js_1.hashHex)(algorithm, salt + number);
return {
algorithm,
challenge,
max,
maxnumber,
salt,
signature: await (0, helpers_js_1.hmac)(algorithm, challenge, options.hmacKey),
signature: await (0, helpers_js_1.hmacHex)(algorithm, challenge, options.hmacKey),
};
}
exports.createChallenge = createChallenge;
Expand All @@ -35,6 +35,39 @@ async function verifySolution(payload, hmacKey) {
check.signature === payload.signature);
}
exports.verifySolution = verifySolution;
async function verifyServerSignature(payload, hmacKey) {
if (typeof payload === 'string') {
payload = JSON.parse(atob(payload));
}
const signature = await (0, helpers_js_1.hmacHex)(payload.algorithm, await (0, helpers_js_1.hash)(payload.algorithm, payload.verificationData), hmacKey);
let verificationData = null;
try {
const params = new URLSearchParams(payload.verificationData);
verificationData = {
...Object.fromEntries(params),
expire: parseInt(params.get('expire') || '0', 10),
fields: params.get('fields')?.split(','),
reasons: params.get('reasons')?.split(','),
score: params.get('score')
? parseFloat(params.get('score') || '0')
: void 0,
time: parseInt(params.get('time') || '0', 10),
verified: params.get('verified') === 'true',
};
}
catch {
// noop
}
return {
verificationData,
verified: payload.verified === true &&
verificationData &&
verificationData.verified === true &&
verificationData.expire > Math.floor(Date.now() / 1000) &&
payload.signature === signature,
};
}
exports.verifyServerSignature = verifyServerSignature;
function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start = 0) {
const controller = new AbortController();
const promise = new Promise((resolve, reject) => {
Expand All @@ -44,7 +77,7 @@ function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start
resolve(null);
}
else {
hashChallenge(salt, n, algorithm)
(0, helpers_js_1.hashHex)(algorithm, salt + n)
.then((t) => {
if (t === challenge) {
resolve({
Expand Down Expand Up @@ -117,12 +150,10 @@ async function solveChallengeWorkers(workerScript, concurrency, challenge, salt,
return solutions.find((solution) => !!solution) || null;
}
exports.solveChallengeWorkers = solveChallengeWorkers;
async function hashChallenge(salt, num, algorithm) {
return (0, helpers_js_1.ab2hex)(await crypto.subtle.digest(algorithm.toUpperCase(), helpers_js_1.encoder.encode(salt + num)));
}
exports.default = {
createChallenge,
verifySolution,
solveChallenge,
solveChallengeWorkers,
verifyServerSignature,
verifySolution,
};
20 changes: 19 additions & 1 deletion cjs/dist/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ export type Algorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
export interface Challenge {
algorithm: Algorithm;
challenge: string;
max?: number;
maxnumber?: number;
salt: string;
signature: string;
}
export interface ChallengeOptions {
algorithm?: Algorithm;
hmacKey: string;
maxnumber?: number;
maxNumber?: number;
number?: number;
salt?: string;
Expand All @@ -21,6 +22,23 @@ export interface Payload {
salt: string;
signature: string;
}
export interface ServerSignaturePayload {
algorithm: Algorithm;
signature: string;
verificationData: string;
verified: boolean;
}
export interface ServerSignatureVerificationData {
classification?: string;
email?: string;
expire: number;
fields?: string[];
fieldsHash?: string;
reasons?: string[];
score?: number;
time: number;
verified: boolean;
}
export interface Solution {
number: number;
took: number;
Expand Down
11 changes: 11 additions & 0 deletions deno_dist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ Parameters:

Returns: `{ controller: AbortController, promise: Promise<Solution | null> }`

### `verifyServerSignature(payload, hmacKey)`

Verifies the server signature returned by the API. The payload can be a Base64-encoded JSON payload or an object.

Parameters:

- `payload: string | ServerSignaturePayload`
- `hmacKey: string`

Returns: `Promise<{ verificationData: ServerSignatureVerificationData | null, verified: boolean }>`

### `solveChallengeWorkers(workerScript, concurrency, challenge, salt, algorithm?, max?, start?)`

Finds a solution to the given challenge with [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) running concurrently.
Expand Down
34 changes: 29 additions & 5 deletions deno_dist/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@ export function ab2hex(ab: ArrayBuffer | Uint8Array) {
.join('');
}

export async function hash(algorithm: Algorithm, str: string) {
return ab2hex(
await crypto.subtle.digest(algorithm.toUpperCase(), encoder.encode(str))
export async function hash(algorithm: Algorithm, data: ArrayBuffer | string) {
return crypto.subtle.digest(
algorithm.toUpperCase(),
typeof data === 'string' ? encoder.encode(data) : new Uint8Array(data)
);
}

export async function hmac(algorithm: Algorithm, str: string, secret: string) {
export async function hashHex(
algorithm: Algorithm,
data: ArrayBuffer | string
) {
return ab2hex(await hash(algorithm, data));
}

export async function hmac(
algorithm: Algorithm,
data: ArrayBuffer | string,
secret: string
) {
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
Expand All @@ -25,7 +37,19 @@ export async function hmac(algorithm: Algorithm, str: string, secret: string) {
false,
['sign', 'verify']
);
return ab2hex(await crypto.subtle.sign('HMAC', key, encoder.encode(str)));
return crypto.subtle.sign(
'HMAC',
key,
typeof data === 'string' ? encoder.encode(data) : new Uint8Array(data)
);
}

export async function hmacHex(
algorithm: Algorithm,
data: ArrayBuffer | string,
secret: string
) {
return ab2hex(await hmac(algorithm, data, secret));
}

export function randomBytes(length: number) {
Expand Down
Loading

0 comments on commit cbc8624

Please sign in to comment.