gnablib
Version:
A lean, zero dependency library to provide a useful base for your project.
2 lines • 4.34 kB
JavaScript
/*! Copyright 2026 the gnablib contributors MPL-1.1 */
import{base32 as t}from"../../codec/Base32.js";import{asBE as e}from"../../endian/platform.js";import{ParseProblem as r}from"../../error/probs/ParseProblem.js";import{U32 as s}from"../../primitive/number/U32Static.js";import{U64 as i,U64Mut as o}from"../../primitive/number/U64.js";import{WindowStr as n}from"../../primitive/WindowStr.js";import{sInt as a,sNum as h}from"../../safe/safe.js";import{Sha1 as c}from"../hash/Sha1.js";import{Sha256 as u,Sha512 as m}from"../hash/Sha2.js";import{Hmac as p}from"../mac/Hmac.js";import{getRandomValues as d}from"crypto";const f=i.fromI32s(1);class _{constructor(t,e,r){this._a8=new Uint8Array(8),this._c=o.mount(new Uint32Array(this._a8.buffer)),this.digits=e,this._mod=Math.pow(10,e),this._hashAlgo=r[Symbol.toStringTag],this._h=new p(r,t)}get counter(){return this._c.clone()}_truncate(t){const e=15&t[t.length-1];return((127&t[e])<<24|t[e+1]<<16|t[e+2]<<8|t[e+3])%this._mod}generate(t=!0){e.i64(this._a8),this._h.write(this._a8),e.i64(this._a8);const r=this._h.sumIn();return this._h.reset(),this._truncate(r)}validate(t,e=1,r=1){let o=this.generate(!1);if(s.ctEq(t,o))return!0;let n=0;const a=i.fromI32s(1);for(;n++<e;)if(this._c.subEq(a),o=this.generate(!1),s.ctEq(t,o))return!0;for(n=0,this._c.addEq(i.fromI32s(e));n++<r;)if(this._c.addEq(a),o=this.generate(!1),s.ctEq(t,o))return!0;return this._c.subEq(i.fromI32s(r)),!1}_uriBase(e,r,s){if(e.indexOf(":")>=0)throw new URIError('Issuer may not include ":"');if(r.indexOf(":")>=0)throw new URIError('User may not include ":"');s instanceof Uint8Array&&(s=t.fromBytes(s,!1));let i=`/${encodeURIComponent(e)}:${encodeURI(r).replace(/\?/g,"%3F")}?secret=${s}`;return"SHA1"!=this._hashAlgo&&(i+=`&algorithm=${this._hashAlgo}`),6!=this.digits&&(i+=`&digits=${this.digits}`),i}static _parseUri(e,s){const i=n.new(e);if(!i.startsWith("otpauth://"))return new r("URI","don't recognize schema",i.substring(0,i.indexOf(":")));if(i.shrink(10),!i.startsWith(s+"/"))return new r("URI","don't recognize type",i.substring(0,i.indexOf("/")));i.shrink(s.length+1);const[o,a]=i.split("?",2),h=o.indexOfAny([":","%3A"]);let c,p;if(h<0?(c="",p=decodeURI(o.toString())):(c=decodeURIComponent(o.substring(0,h)),p=":"==o.charAt(h)?decodeURI(o.substring(h+1)).trim():decodeURI(o.substring(h+3)).trim()),!a)return new r("secret","required");const d=a.split("&");let f,_,g=6,w=0,l=30;for(const e of d){const[s,i]=e.toString().split("=");switch(s){case"secret":f=t.toBytes(i);break;case"digits":g=Number.parseInt(i,10);break;case"algorithm":switch(i.toUpperCase()){case"SHA1":break;case"SHA256":_=new u;break;case"SHA512":_=new m;break;default:return new r("algorithm","Unknown value",i)}break;case"counter":w=Number.parseInt(i,10);break;case"period":l=Number.parseInt(i,10)}}return f&&0!=f.length?[c,p,f,_,g,w,l]:new r("secret","required")}static randomKey(t){t?h("size",t).atLeast(4).throwNot():t=16;const e=new Uint8Array(t);return d(e),e}}export class Hotp extends _{createUri(t,e,r){return`otpauth://hotp${this._uriBase(t,e,r)}&counter=${this._c.toDecStr()}`}consume(){this._c.addEq(f)}static newWithKey(t,e,r,s){e?h("digits",e).atLeast(6).atMost(10).throwNot():e=6,r?h("algo.length",r.size).atLeast(20).throwNot():r=new c;const i=new Hotp(t,e,r);return s&&i._c.set(s),i}static parseUri(t){const e=super._parseUri(t,"hotp");if(e instanceof r)return e;const[s,o,n,h,c,u]=e;a("counter",u).unsigned().throwNot();return[Hotp.newWithKey(n,c,h,i.fromInt(u)),s,o]}}export class Totp extends _{constructor(t,e,r,s){super(t,r,s),this._x=e}get timeStep(){return this._x}_setNow(){const t=o.fromInt(Math.floor(Date.now()/1e3)).divEq32(this._x);this._c.set(t)}setUtime(t){const e=t.mut().divEq32(this._x);this._c.set(e)}generate(t=!0){return t&&this._setNow(),super.generate()}validate(t,e=1,r=1,s=!0){return s&&this._setNow(),super.validate(t,e,r)}createUri(t,e,r){let s=`otpauth://totp${this._uriBase(t,e,r)}`;return 30!=this._x&&(s+=`&period=${this._x}`),s}static newWithKey(t,e,r,s){return e?a("timeStep",e).atLeast(10).atMost(600).throwNot():e=30,r?h("digits",r).atLeast(6).atMost(10).throwNot():r=6,s?h("algo.length",s.size).atLeast(20).throwNot():s=new c,new Totp(t,e,r,s)}static parseUri(t){const e=super._parseUri(t,"totp");if(e instanceof r)return e;const[s,i,o,n,a,,h]=e;return[Totp.newWithKey(o,h,a,n),s,i]}}