blob: 5a3d825c7e72b5f1d7d28f9f2931259ac0a24d4d [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
/* MPIN API Functions */
var MPIN = {
BAD_PARAMS:-11,
INVALID_POINT:-14,
WRONG_ORDER:-18,
BAD_PIN:-19,
/* configure PIN here */
MAXPIN:10000, /* max PIN */
PBLEN:14, /* MAXPIN length in bits */
TS:10, /* 10 for 4 digit PIN, 14 for 6-digit PIN - 2^TS/TS approx = sqrt(MAXPIN) */
TRAP:200, /* 200 for 4 digit PIN, 2000 for 6-digit PIN - approx 2*sqrt(MAXPIN) */
EFS:ROM.MODBYTES,
EGS:ROM.MODBYTES,
PAS:16,
/* return time in slots since epoch */
today: function() {
var now=new Date();
return Math.floor(now.getTime()/(60000*1440)); // for daily tokens
},
bytestostring: function(b)
{
var s="";
var len=b.length;
var ch;
for (var i=0;i<len;i++)
{
ch=b[i];
s+=((ch>>>4)&15).toString(16);
s+=(ch&15).toString(16);
}
return s;
},
stringtobytes: function(s)
{
var b=[];
for (var i=0;i<s.length;i++)
b.push(s.charCodeAt(i));
return b;
},
comparebytes: function(a,b)
{
if (a.length!=b.length) return false;
for (var i=0;i<a.length;i++)
{
if (a[i]!=b[i]) return false;
}
return true;
},
/* Hash number (optional) and string to point on curve */
hashit: function(n,ID)
{
var H=new HASH();
if (n!==0) H.process_num(n);
H.process_array(ID);
return H.hash();
},
mapit: function(h)
{
var q=new BIG(0); q.rcopy(ROM.Modulus);
var x=BIG.fromBytes(h);
x.mod(q);
var P=new ECP();
while (true)
{
P.setxi(x,0);
if (!P.is_infinity()) break;
x.inc(1); x.norm();
}
return P;
},
/* needed for SOK */
mapit2: function(h)
{
var q=new BIG(0); q.rcopy(ROM.Modulus);
var x=BIG.fromBytes(h);
var one=new BIG(1);
x.mod(q);
var Q,T,K,X;
while (true)
{
X=new FP2(one,x);
Q=new ECP2(); Q.setx(X);
if (!Q.is_infinity()) break;
x.inc(1); x.norm();
}
/* Fast Hashing to G2 - Fuentes-Castaneda, Knapp and Rodriguez-Henriquez */
var Fa=new BIG(0); Fa.rcopy(ROM.CURVE_Fra);
var Fb=new BIG(0); Fb.rcopy(ROM.CURVE_Frb);
X=new FP2(Fa,Fb);
x=new BIG(0); x.rcopy(ROM.CURVE_Bnx);
T=new ECP2(); T.copy(Q);
T.mul(x); T.neg();
K=new ECP2(); K.copy(T);
K.dbl(); K.add(T); K.affine();
K.frob(X);
Q.frob(X); Q.frob(X); Q.frob(X);
Q.add(T); Q.add(K);
T.frob(X); T.frob(X);
Q.add(T);
Q.affine();
return Q;
},
/* these next two functions help to implement elligator squared - http://eprint.iacr.org/2014/043 */
/* maps a random u to a point on the curve */
map: function(u,cb)
{
var P=new ECP();
var x=new BIG(u);
var p=new BIG(0); p.rcopy(ROM.Modulus);
x.mod(p);
while (true)
{
P.setxi(x,cb);
if (!P.is_infinity()) break;
x.inc(1); x.norm();
}
return P;
},
/* returns u derived from P. Random value in range 1 to return value should then be added to u */
unmap: function(u,P)
{
var s=P.getS();
var R=new ECP();
var r=0;
var x=P.getX();
u.copy(x);
while (true)
{
u.dec(1); u.norm();
r++;
R.setxi(u,s); //=new ECP(u,s);
if (!R.is_infinity()) break;
}
return r;
},
/* these next two functions implement elligator squared - http://eprint.iacr.org/2014/043 */
/* Elliptic curve point E in format (0x04,x,y} is converted to form {0x0-,u,v} */
/* Note that u and v are indistinguisible from random strings */
ENCODING: function(rng,E)
{
var i,rn,m,su,sv;
var T=[];
for (i=0;i<this.EFS;i++) T[i]=E[i+1];
var u=BIG.fromBytes(T);
for (i=0;i<this.EFS;i++) T[i]=E[i+this.EFS+1];
var v=BIG.fromBytes(T);
var P=new ECP(0); P.setxy(u,v);
if (P.is_infinity()) return this.INVALID_POINT;
var p=new BIG(0); p.rcopy(ROM.Modulus);
u=BIG.randomnum(p,rng);
su=rng.getByte(); if (su<0) su=-su; su%=2;
var W=this.map(u,su);
P.sub(W);
sv=P.getS();
rn=this.unmap(v,P);
m=rng.getByte(); if (m<0) m=-m; m%=rn;
v.inc(m+1);
E[0]=(su+2*sv);
u.toBytes(T);
for (i=0;i<this.EFS;i++) E[i+1]=T[i];
v.toBytes(T);
for (i=0;i<this.EFS;i++) E[i+this.EFS+1]=T[i];
return 0;
},
DECODING: function(D)
{
var i,su,sv;
var T=[];
if ((D[0]&0x04)!==0) return this.INVALID_POINT;
for (i=0;i<this.EFS;i++) T[i]=D[i+1];
var u=BIG.fromBytes(T);
for (i=0;i<this.EFS;i++) T[i]=D[i+this.EFS+1];
var v=BIG.fromBytes(T);
su=D[0]&1;
sv=(D[0]>>1)&1;
var W=this.map(u,su);
var P=this.map(v,sv);
P.add(W);
u=P.getX();
v=P.getY();
D[0]=0x04;
u.toBytes(T);
for (i=0;i<this.EFS;i++) D[i+1]=T[i];
v.toBytes(T);
for (i=0;i<this.EFS;i++) D[i+this.EFS+1]=T[i];
return 0;
},
/* R=R1+R2 in group G1 */
RECOMBINE_G1: function(R1,R2,R)
{
var P=ECP.fromBytes(R1);
var Q=ECP.fromBytes(R2);
if (P.is_infinity() || Q.is_infinity()) return this.INVALID_POINT;
P.add(Q);
P.toBytes(R);
return 0;
},
/* W=W1+W2 in group G2 */
RECOMBINE_G2: function(W1,W2,W)
{
var P=ECP2.fromBytes(W1);
var Q=ECP2.fromBytes(W2);
if (P.is_infinity() || Q.is_infinity()) return this.INVALID_POINT;
P.add(Q);
P.toBytes(W);
return 0;
},
HASH_ID: function(ID)
{
return this.hashit(0,ID);
},
/* create random secret S */
RANDOM_GENERATE: function(rng,S)
{
var r=new BIG(0); r.rcopy(ROM.CURVE_Order);
var s=BIG.randomnum(r,rng);
s.toBytes(S);
return 0;
},
/* Extract PIN from TOKEN for identity CID */
EXTRACT_PIN: function(CID,pin,TOKEN)
{
var P=ECP.fromBytes(TOKEN);
if (P.is_infinity()) return this.INVALID_POINT;
var h=this.hashit(0,CID);
var R=this.mapit(h);
pin%=this.MAXPIN;
R=R.pinmul(pin,this.PBLEN);
P.sub(R);
P.toBytes(TOKEN);
return 0;
},
/* Extract Server Secret SST=S*Q where Q is fixed generator in G2 and S is master secret */
GET_SERVER_SECRET: function(S,SST)
{
var A=new BIG(0);
var B=new BIG(0);
A.rcopy(ROM.CURVE_Pxa); B.rcopy(ROM.CURVE_Pxb);
var QX=new FP2(0); QX.bset(A,B);
A.rcopy(ROM.CURVE_Pya); B.rcopy(ROM.CURVE_Pyb);
var QY=new FP2(0); QY.bset(A,B);
var Q=new ECP2();
Q.setxy(QX,QY);
var s=BIG.fromBytes(S);
Q=PAIR.G2mul(Q,s);
Q.toBytes(SST);
return 0;
},
/*
W=x*H(G);
if RNG == NULL then X is passed in
if RNG != NULL the X is passed out
if type=0 W=x*G where G is point on the curve, else W=x*M(G), where M(G) is mapping of octet G to point on the curve
*/
GET_G1_MULTIPLE: function(rng,type,X,G,W)
{
var x;
var r=new BIG(0); r.rcopy(ROM.CURVE_Order);
if (rng!=null)
{
x=BIG.randomnum(r,rng);
x.toBytes(X);
}
else
{
x=BIG.fromBytes(X);
}
var P;
if (type==0)
{
P=ECP.fromBytes(G);
if (P.is_infinity()) return INVALID_POINT;
}
else
P=this.mapit(G);
PAIR.G1mul(P,x).toBytes(W);
return 0;
},
/* Client secret CST=S*H(CID) where CID is client ID and S is master secret */
GET_CLIENT_SECRET: function(S,CID,CST)
{
return this.GET_G1_MULTIPLE(null,1,S,CID,CST);
},
/* Time Permit CTT=S*(date|H(CID)) where S is master secret */
GET_CLIENT_PERMIT: function(date,S,CID,CTT)
{
var h=this.hashit(date,CID);
var P=this.mapit(h);
var s=BIG.fromBytes(S);
P=PAIR.G1mul(P,s);
P.toBytes(CTT);
return 0;
},
/* Implement step 1 on client side of MPin protocol */
CLIENT_1: function(date,CLIENT_ID,rng,X,pin,TOKEN,SEC,xID,xCID,PERMIT)
{
var r=new BIG(0); r.rcopy(ROM.CURVE_Order);
// var q=new BIG(0); q.rcopy(ROM.Modulus);
var x;
if (rng!==null)
{
x=BIG.randomnum(r,rng);
x.toBytes(X);
}
else
{
x=BIG.fromBytes(X);
}
var P,T,W;
var h=this.hashit(0,CLIENT_ID);
P=this.mapit(h);
T=ECP.fromBytes(TOKEN);
if (T.is_infinity()) return this.INVALID_POINT;
pin%=this.MAXPIN;
W=P.pinmul(pin,this.PBLEN);
T.add(W);
if (date!=0)
{
W=ECP.fromBytes(PERMIT);
if (W.is_infinity()) return this.INVALID_POINT;
T.add(W);
h=this.hashit(date,h);
W=this.mapit(h);
if (xID!=null)
{
P=PAIR.G1mul(P,x);
P.toBytes(xID);
W=PAIR.G1mul(W,x);
P.add(W);
}
else
{
P.add(W);
P=PAIR.G1mul(P,x);
}
if (xCID!=null) P.toBytes(xCID);
}
else
{
if (xID!=null)
{
P=PAIR.G1mul(P,x);
P.toBytes(xID);
}
}
T.toBytes(SEC);
return 0;
},
/* Implement step 2 on client side of MPin protocol */
CLIENT_2: function(X,Y,SEC)
{
var r=new BIG(0); r.rcopy(ROM.CURVE_Order);
var P=ECP.fromBytes(SEC);
if (P.is_infinity()) return this.INVALID_POINT;
var px=BIG.fromBytes(X);
var py=BIG.fromBytes(Y);
px.add(py);
px.mod(r);
px.rsub(r);
PAIR.G1mul(P,px).toBytes(SEC);
return 0;
},
/* Outputs H(CID) and H(T|H(CID)) for time permits. If no time permits set HID=HTID */
SERVER_1: function(date,CID,HID,HTID)
{
var h=this.hashit(0,CID);
var R,P=this.mapit(h);
if (date!==0)
{
if (HID!=null) P.toBytes(HID);
h=this.hashit(date,h);
R=this.mapit(h);
P.add(R);
P.toBytes(HTID);
}
else P.toBytes(HID);
},
/* Implement step 1 of MPin protocol on server side */
SERVER_2: function(date,HID,HTID,Y,SST,xID,xCID,mSEC,E,F)
{
var A=new BIG(0);
var B=new BIG(0);
A.rcopy(ROM.CURVE_Pxa); B.rcopy(ROM.CURVE_Pxb);
var QX=new FP2(0); QX.bset(A,B);
A.rcopy(ROM.CURVE_Pya); B.rcopy(ROM.CURVE_Pyb);
var QY=new FP2(0); QY.bset(A,B);
var Q=new ECP2();
Q.setxy(QX,QY);
var sQ=ECP2.fromBytes(SST);
if (sQ.is_infinity()) return this.INVALID_POINT;
var R;
if (date!==0)
R=ECP.fromBytes(xCID);
else
{
if (xID==null) return this.BAD_PARAMS;
R=ECP.fromBytes(xID);
}
if (R.is_infinity()) return this.INVALID_POINT;
var y=BIG.fromBytes(Y);
var P;
if (date!=0) P=ECP.fromBytes(HTID);
else
{
if (HID==null) return this.BAD_PARAMS;
P=ECP.fromBytes(HID);
}
if (P.is_infinity()) return this.INVALID_POINT;
P=PAIR.G1mul(P,y);
P.add(R);
R=ECP.fromBytes(mSEC);
if (R.is_infinity()) return this.INVALID_POINT;
var g=PAIR.ate2(Q,R,sQ,P);
g=PAIR.fexp(g);
if (!g.isunity())
{
if (HID!=null && xID!=null && E!=null && F!=null)
{
g.toBytes(E);
if (date!==0)
{
P=ECP.fromBytes(HID);
if (P.is_infinity()) return this.INVALID_POINT;
R=ECP.fromBytes(xID);
if (R.is_infinity()) return this.INVALID_POINT;
P=PAIR.G1mul(P,y);
P.add(R);
}
g=PAIR.ate(Q,P);
g=PAIR.fexp(g);
g.toBytes(F);
}
return this.BAD_PIN;
}
return 0;
},
/* Pollards kangaroos used to return PIN error */
KANGAROO: function(E,F)
{
var ge=FP12.fromBytes(E);
var gf=FP12.fromBytes(F);
var distance = [];
var t=new FP12(gf);
var table=[];
var i,j,m,s,dn,dm,res,steps;
s=1;
for (m=0;m<this.TS;m++)
{
distance[m]=s;
table[m]=new FP12(t);
s*=2;
t.usqr();
}
t.one();
dn=0;
for (j=0;j<this.TRAP;j++)
{
i=t.geta().geta().getA().lastbits(8)%this.TS;
t.mul(table[i]);
dn+=distance[i];
}
gf.copy(t); gf.conj();
steps=0; dm=0;
res=0;
while (dm-dn<this.MAXPIN)
{
steps++;
if (steps>4*this.TRAP) break;
i=ge.geta().geta().getA().lastbits(8)%this.TS;
ge.mul(table[i]);
dm+=distance[i];
if (ge.equals(t))
{
res=dm-dn;
break;
}
if (ge.equals(gf))
{
res=dn-dm;
break;
}
}
if (steps>4*this.TRAP || dm-dn>=this.MAXPIN) {res=0; } // Trap Failed - probable invalid token
return res;
},
/* return time since epoch */
GET_TIME: function() {
var now=new Date();
return Math.floor(now.getTime()/(1000));
},
/* y = H(time,xCID) */
GET_Y: function(TimeValue,xCID,Y)
{
var q=new BIG(0);
q.rcopy(ROM.CURVE_Order);
var h=this.hashit(TimeValue,xCID);
var y=BIG.fromBytes(h);
y.mod(q);
y.toBytes(Y);
return 0;
},
/* One pass MPIN Client */
CLIENT: function(date,CLIENT_ID,rng,X,pin,TOKEN,SEC,xID,xCID,PERMIT,TimeValue,Y)
{
var rtn=0;
var pID;
if (date == 0) {
pID = xID;
} else {
pID = xCID;
xID = null;
}
rtn = this.CLIENT_1(date,CLIENT_ID,rng,X,pin,TOKEN,SEC,xID,xCID,PERMIT);
if (rtn != 0)
return rtn;
this.GET_Y(TimeValue,pID,Y);
rtn = this.CLIENT_2(X,Y,SEC);
if (rtn != 0)
return rtn;
return 0;
},
/* One pass MPIN Server */
SERVER: function(date,HID,HTID,Y,SST,xID,xCID,mSEC,E,F,CID,TimeValue)
{
var rtn=0;
this.SERVER_1(date,CID,HID,HTID);
this.GET_Y(TimeValue,xCID,Y);
rtn = this.SERVER_2(date,HID,HTID,Y,SST,xID,xCID,SEC,E,F);
if (rtn != 0)
return rtn;
return 0;
},
/* Functions to support M-Pin Full */
PRECOMPUTE: function(TOKEN,CID,G1,G2)
{
var P,T;
var g;
T=ECP.fromBytes(TOKEN);
if (T.is_infinity()) return INVALID_POINT;
P=this.mapit(CID);
var A=new BIG(0);
var B=new BIG(0);
A.rcopy(ROM.CURVE_Pxa); B.rcopy(ROM.CURVE_Pxb);
var QX=new FP2(0); QX.bset(A,B);
A.rcopy(ROM.CURVE_Pya); B.rcopy(ROM.CURVE_Pyb);
var QY=new FP2(0); QY.bset(A,B);
var Q=new ECP2();
Q.setxy(QX,QY);
g=PAIR.ate(Q,T);
g=PAIR.fexp(g);
g.toBytes(G1);
g=PAIR.ate(Q,P);
g=PAIR.fexp(g);
g.toBytes(G2);
return 0;
},
/* calculate common key on client side */
/* wCID = w.(A+AT) */
CLIENT_KEY: function(G1,G2,pin,R,X,wCID,CK)
{
var H=new HASH();
var t=[];
var g1=FP12.fromBytes(G1);
var g2=FP12.fromBytes(G2);
var z=BIG.fromBytes(R);
var x=BIG.fromBytes(X);
var W=ECP.fromBytes(wCID);
if (W.is_infinity()) return INVALID_POINT;
W=PAIR.G1mul(W,x);
var fa=new BIG(0); fa.rcopy(ROM.CURVE_Fra);
var fb=new BIG(0); fb.rcopy(ROM.CURVE_Frb);
var f=new FP2(fa,fb); //f.bset(fa,fb);
var r=new BIG(0); r.rcopy(ROM.CURVE_Order);
var q=new BIG(0); q.rcopy(ROM.Modulus);
var m=new BIG(q);
m.mod(r);
var a=new BIG(z);
a.mod(m);
var b=new BIG(z);
b.div(m);
g2.pinpow(pin,this.PBLEN);
g1.mul(g2);
var c=g1.trace();
g2.copy(g1);
g2.frob(f);
var cp=g2.trace();
g1.conj();
g2.mul(g1);
var cpm1=g2.trace();
g2.mul(g1);
var cpm2=g2.trace();
c=c.xtr_pow2(cp,cpm1,cpm2,a,b);
c.geta().getA().toBytes(t);
H.process_array(t);
c.geta().getB().toBytes(t);
H.process_array(t);
c.getb().getA().toBytes(t);
H.process_array(t);
c.getb().getB().toBytes(t);
H.process_array(t);
W.getX().toBytes(t);
H.process_array(t);
W.getY().toBytes(t);
H.process_array(t);
t=H.hash();
for (var i=0;i<this.PAS;i++) CK[i]=t[i];
return 0;
},
/* calculate common key on server side */
/* Z=r.A - no time permits involved */
SERVER_KEY: function(Z,SST,W,xID,xCID,SK)
{
var H=new HASH();
var t=[];
var sQ=ECP2.fromBytes(SST);
if (sQ.is_infinity()) return INVALID_POINT;
var R=ECP.fromBytes(Z);
if (R.is_infinity()) return INVALID_POINT;
var U;
if (xCID!=null)
U=ECP.fromBytes(xCID);
else
U=ECP.fromBytes(xID);
if (U.is_infinity()) return INVALID_POINT;
var w=BIG.fromBytes(W);
U=PAIR.G1mul(U,w);
var g=PAIR.ate(sQ,R);
g=PAIR.fexp(g);
var c=g.trace();
c.geta().getA().toBytes(t);
H.process_array(t);
c.geta().getB().toBytes(t);
H.process_array(t);
c.getb().getA().toBytes(t);
H.process_array(t);
c.getb().getB().toBytes(t);
H.process_array(t);
U.getX().toBytes(t);
H.process_array(t);
U.getY().toBytes(t);
H.process_array(t);
t=H.hash();
for (var i=0;i<this.PAS;i++) SK[i]=t[i];
return 0;
}
};