blob: f8ae051746505313669fa478bc3df7df27d6a8d1 [file] [log] [blame]
using System;
/*
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 */
public class MPIN
{
public static readonly int EFS = ROM.MODBYTES;
public static readonly int EGS = ROM.MODBYTES;
public const int PAS = 16;
public const int BAD_PARAMS = -11;
public const int INVALID_POINT = -14;
public const int WRONG_ORDER = -18;
public const int BAD_PIN = -19;
/* Configure your PIN here */
public const int MAXPIN = 10000; // PIN less than this
public const int PBLEN = 14; // Number of bits in PIN
public const int TS = 10; // 10 for 4 digit PIN, 14 for 6-digit PIN - 2^TS/TS approx = sqrt(MAXPIN)
public const int TRAP = 200; // 200 for 4 digit PIN, 2000 for 6-digit PIN - approx 2*sqrt(MAXPIN)
/* Hash number (optional) and string to point on curve */
public static sbyte[] hashit(int n, sbyte[] ID)
{
HASH H = new HASH();
if (n != 0)
{
H.process_num(n);
}
H.process_array(ID);
sbyte[] h = H.hash();
return h;
}
public static ECP mapit(sbyte[] h)
{
BIG q = new BIG(ROM.Modulus);
BIG x = BIG.fromBytes(h);
x.mod(q);
ECP P;
while (true)
{
P = new ECP(x,0);
if (!P.is_infinity())
{
break;
}
x.inc(1);
x.norm();
}
return P;
}
/* needed for SOK */
public static ECP2 mapit2(sbyte[] h)
{
BIG q = new BIG(ROM.Modulus);
BIG x = BIG.fromBytes(h);
BIG one = new BIG(1);
FP2 X;
ECP2 Q, T, K;
x.mod(q);
while (true)
{
X = new FP2(one,x);
Q = new ECP2(X);
if (!Q.is_infinity())
{
break;
}
x.inc(1);
x.norm();
}
/* Fast Hashing to G2 - Fuentes-Castaneda, Knapp and Rodriguez-Henriquez */
BIG Fra = new BIG(ROM.CURVE_Fra);
BIG Frb = new BIG(ROM.CURVE_Frb);
X = new FP2(Fra,Frb);
x = new BIG(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;
}
/* return time in slots since epoch */
public static int today()
{
TimeSpan t = DateTime.Now- new DateTime(1970,1,1);
return (int)(t.TotalSeconds / (60 * 1440));
}
/* 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 */
public static ECP map(BIG u, int cb)
{
ECP P;
BIG x = new BIG(u);
BIG p = new BIG(ROM.Modulus);
x.mod(p);
while (true)
{
P = new ECP(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 */
public static int unmap(BIG u, ECP P)
{
int s = P.S;
ECP R;
int r = 0;
BIG x = P.X;
u.copy(x);
while (true)
{
u.dec(1);
u.norm();
r++;
R = new ECP(u,s);
if (!R.is_infinity())
{
break;
}
}
return r;
}
public static sbyte[] HASH_ID(sbyte[] ID)
{
return hashit(0,ID);
}
/* 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 */
public static int ENCODING(RAND rng, sbyte[] E)
{
int rn, m, su, sv;
sbyte[] T = new sbyte[EFS];
for (int i = 0;i < EFS;i++)
{
T[i] = E[i + 1];
}
BIG u = BIG.fromBytes(T);
for (int i = 0;i < EFS;i++)
{
T[i] = E[i + EFS + 1];
}
BIG v = BIG.fromBytes(T);
ECP P = new ECP(u,v);
if (P.is_infinity())
{
return INVALID_POINT;
}
BIG p = new BIG(ROM.Modulus);
u = BIG.randomnum(p,rng);
su = rng.Byte; //if (su<0) su=-su;
su %= 2;
ECP W = map(u,su);
P.sub(W);
sv = P.S;
rn = unmap(v,P);
m = rng.Byte; //if (m<0) m=-m;
m %= rn;
v.inc(m + 1);
E[0] = (sbyte)(su + 2 * sv);
u.toBytes(T);
for (int i = 0;i < EFS;i++)
{
E[i + 1] = T[i];
}
v.toBytes(T);
for (int i = 0;i < EFS;i++)
{
E[i + EFS + 1] = T[i];
}
return 0;
}
public static int DECODING(sbyte[] D)
{
int su, sv;
sbyte[] T = new sbyte[EFS];
if ((D[0] & 0x04) != 0)
{
return INVALID_POINT;
}
for (int i = 0;i < EFS;i++)
{
T[i] = D[i + 1];
}
BIG u = BIG.fromBytes(T);
for (int i = 0;i < EFS;i++)
{
T[i] = D[i + EFS + 1];
}
BIG v = BIG.fromBytes(T);
su = D[0] & 1;
sv = (D[0] >> 1) & 1;
ECP W = map(u,su);
ECP P = map(v,sv);
P.add(W);
u = P.X;
v = P.Y;
D[0] = 0x04;
u.toBytes(T);
for (int i = 0;i < EFS;i++)
{
D[i + 1] = T[i];
}
v.toBytes(T);
for (int i = 0;i < EFS;i++)
{
D[i + EFS + 1] = T[i];
}
return 0;
}
/* R=R1+R2 in group G1 */
public static int RECOMBINE_G1(sbyte[] R1, sbyte[] R2, sbyte[] R)
{
ECP P = ECP.fromBytes(R1);
ECP Q = ECP.fromBytes(R2);
if (P.is_infinity() || Q.is_infinity())
{
return INVALID_POINT;
}
P.add(Q);
P.toBytes(R);
return 0;
}
/* W=W1+W2 in group G2 */
public static int RECOMBINE_G2(sbyte[] W1, sbyte[] W2, sbyte[] W)
{
ECP2 P = ECP2.fromBytes(W1);
ECP2 Q = ECP2.fromBytes(W2);
if (P.is_infinity() || Q.is_infinity())
{
return INVALID_POINT;
}
P.add(Q);
P.toBytes(W);
return 0;
}
/* create random secret S */
public static int RANDOM_GENERATE(RAND rng, sbyte[] S)
{
BIG s;
BIG r = new BIG(ROM.CURVE_Order);
s = BIG.randomnum(r,rng);
s.toBytes(S);
return 0;
}
/* Extract PIN from TOKEN for identity CID */
public static int EXTRACT_PIN(sbyte[] CID, int pin, sbyte[] TOKEN)
{
ECP P = ECP.fromBytes(TOKEN);
if (P.is_infinity())
{
return INVALID_POINT;
}
sbyte[] h = hashit(0,CID);
ECP R = mapit(h);
pin %= MAXPIN;
R = R.pinmul(pin,PBLEN);
P.sub(R);
P.toBytes(TOKEN);
return 0;
}
/* Implement step 2 on client side of MPin protocol */
public static int CLIENT_2(sbyte[] X, sbyte[] Y, sbyte[] SEC)
{
BIG r = new BIG(ROM.CURVE_Order);
ECP P = ECP.fromBytes(SEC);
if (P.is_infinity())
{
return INVALID_POINT;
}
BIG px = BIG.fromBytes(X);
BIG py = BIG.fromBytes(Y);
px.add(py);
px.mod(r);
px.rsub(r);
PAIR.G1mul(P,px).toBytes(SEC);
return 0;
}
/* Implement step 1 on client side of MPin protocol */
public static int CLIENT_1(int date, sbyte[] CLIENT_ID, RAND rng, sbyte[] X, int pin, sbyte[] TOKEN, sbyte[] SEC, sbyte[] xID, sbyte[] xCID, sbyte[] PERMIT)
{
BIG r = new BIG(ROM.CURVE_Order);
// BIG q=new BIG(ROM.Modulus);
BIG x;
// BIG m=new BIG(0);
if (rng != null)
{
x = BIG.randomnum(r,rng);
x.toBytes(X);
}
else
{
x = BIG.fromBytes(X);
}
ECP P, T, W;
BIG px;
// byte[] t=new byte[EFS];
sbyte[] h = hashit(0,CLIENT_ID);
P = mapit(h);
T = ECP.fromBytes(TOKEN);
if (T.is_infinity())
{
return INVALID_POINT;
}
pin %= MAXPIN;
W = P.pinmul(pin,PBLEN);
T.add(W);
if (date != 0)
{
W = ECP.fromBytes(PERMIT);
if (W.is_infinity())
{
return INVALID_POINT;
}
T.add(W);
h = hashit(date,h);
W = 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;
}
/* Extract Server Secret SST=S*Q where Q is fixed generator in G2 and S is master secret */
public static int GET_SERVER_SECRET(sbyte[] S, sbyte[] SST)
{
ECP2 Q = new ECP2(new FP2(new BIG(ROM.CURVE_Pxa),new BIG(ROM.CURVE_Pxb)),new FP2(new BIG(ROM.CURVE_Pya),new BIG(ROM.CURVE_Pyb)));
BIG 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
*/
public static int GET_G1_MULTIPLE(RAND rng, int type, sbyte[] X, sbyte[] G, sbyte[] W)
{
BIG x;
BIG r = new BIG(ROM.CURVE_Order);
if (rng != null)
{
x = BIG.randomnum(r,rng);
x.toBytes(X);
}
else
{
x = BIG.fromBytes(X);
}
ECP P;
if (type == 0)
{
P = ECP.fromBytes(G);
if (P.is_infinity())
{
return INVALID_POINT;
}
}
else
{
P = 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 */
/* CID is hashed externally */
public static int GET_CLIENT_SECRET(sbyte[] S, sbyte[] CID, sbyte[] CST)
{
return GET_G1_MULTIPLE(null,1,S,CID,CST);
}
/* Time Permit CTT=S*(date|H(CID)) where S is master secret */
public static int GET_CLIENT_PERMIT(int date, sbyte[] S, sbyte[] CID, sbyte[] CTT)
{
sbyte[] h = hashit(date,CID);
ECP P = mapit(h);
BIG s = BIG.fromBytes(S);
PAIR.G1mul(P,s).toBytes(CTT);
return 0;
}
/* Outputs H(CID) and H(T|H(CID)) for time permits. If no time permits set HID=HTID */
public static void SERVER_1(int date, sbyte[] CID, sbyte[] HID, sbyte[] HTID)
{
sbyte[] h = hashit(0,CID);
ECP R , P = mapit(h);
if (date != 0)
{
if (HID != null)
{
P.toBytes(HID);
}
h = hashit(date,h);
R = mapit(h);
P.add(R);
P.toBytes(HTID);
}
else
{
P.toBytes(HID);
}
}
/* Implement step 2 of MPin protocol on server side */
public static int SERVER_2(int date, sbyte[] HID, sbyte[] HTID, sbyte[] Y, sbyte[] SST, sbyte[] xID, sbyte[] xCID, sbyte[] mSEC, sbyte[] E, sbyte[] F)
{
BIG q = new BIG(ROM.Modulus);
ECP2 Q = new ECP2(new FP2(new BIG(ROM.CURVE_Pxa),new BIG(ROM.CURVE_Pxb)),new FP2(new BIG(ROM.CURVE_Pya),new BIG(ROM.CURVE_Pyb)));
ECP2 sQ = ECP2.fromBytes(SST);
if (sQ.is_infinity())
{
return INVALID_POINT;
}
ECP R;
if (date != 0)
{
R = ECP.fromBytes(xCID);
}
else
{
if (xID == null)
{
return BAD_PARAMS;
}
R = ECP.fromBytes(xID);
}
if (R.is_infinity())
{
return INVALID_POINT;
}
BIG y = BIG.fromBytes(Y);
ECP P;
if (date != 0)
{
P = ECP.fromBytes(HTID);
}
else
{
if (HID == null)
{
return BAD_PARAMS;
}
P = ECP.fromBytes(HID);
}
if (P.is_infinity())
{
return INVALID_POINT;
}
P = PAIR.G1mul(P,y);
P.add(R);
R = ECP.fromBytes(mSEC);
if (R.is_infinity())
{
return INVALID_POINT;
}
FP12 g;
// FP12 g1=new FP12(0);
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 INVALID_POINT;
}
R = ECP.fromBytes(xID);
if (R.is_infinity())
{
return INVALID_POINT;
}
P = PAIR.G1mul(P,y);
P.add(R);
}
g = PAIR.ate(Q,P);
g = PAIR.fexp(g);
g.toBytes(F);
}
return BAD_PIN;
}
return 0;
}
/* Pollards kangaroos used to return PIN error */
public static int KANGAROO(sbyte[] E, sbyte[] F)
{
FP12 ge = FP12.fromBytes(E);
FP12 gf = FP12.fromBytes(F);
int[] distance = new int[TS];
FP12 t = new FP12(gf);
FP12[] table = new FP12[TS];
int i, j, m, s, dn, dm, res, steps;
s = 1;
for (m = 0;m < TS;m++)
{
distance[m] = s;
table[m] = new FP12(t);
s *= 2;
t.usqr();
}
t.one();
dn = 0;
for (j = 0;j < TRAP;j++)
{
i = t.geta().geta().A.lastbits(8) % TS;
t.mul(table[i]);
dn += distance[i];
}
gf.copy(t);
gf.conj();
steps = 0;
dm = 0;
res = 0;
while (dm - dn < MAXPIN)
{
steps++;
if (steps > 4 * TRAP)
{
break;
}
i = ge.geta().geta().A.lastbits(8) % 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 * TRAP || dm - dn >= MAXPIN)
{
res = 0;
} // Trap Failed - probable invalid token
return res;
}
/* Functions to support M-Pin Full */
public static int PRECOMPUTE(sbyte[] TOKEN, sbyte[] CID, sbyte[] G1, sbyte[] G2)
{
ECP P, T;
FP12 g;
T = ECP.fromBytes(TOKEN);
if (T.is_infinity())
{
return INVALID_POINT;
}
P = mapit(CID);
ECP2 Q = new ECP2(new FP2(new BIG(ROM.CURVE_Pxa),new BIG(ROM.CURVE_Pxb)),new FP2(new BIG(ROM.CURVE_Pya),new BIG(ROM.CURVE_Pyb)));
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) */
public static int CLIENT_KEY(sbyte[] G1, sbyte[] G2, int pin, sbyte[] R, sbyte[] X, sbyte[] wCID, sbyte[] CK)
{
HASH H = new HASH();
sbyte[] t = new sbyte[EFS];
FP12 g1 = FP12.fromBytes(G1);
FP12 g2 = FP12.fromBytes(G2);
BIG z = BIG.fromBytes(R);
BIG x = BIG.fromBytes(X);
ECP W = ECP.fromBytes(wCID);
if (W.is_infinity())
{
return INVALID_POINT;
}
W = PAIR.G1mul(W,x);
FP2 f = new FP2(new BIG(ROM.CURVE_Fra),new BIG(ROM.CURVE_Frb));
BIG r = new BIG(ROM.CURVE_Order);
BIG q = new BIG(ROM.Modulus);
BIG m = new BIG(q);
m.mod(r);
BIG a = new BIG(z);
a.mod(m);
BIG b = new BIG(z);
b.div(m);
g2.pinpow(pin,PBLEN);
g1.mul(g2);
FP4 c = g1.trace();
g2.copy(g1);
g2.frob(f);
FP4 cp = g2.trace();
g1.conj();
g2.mul(g1);
FP4 cpm1 = g2.trace();
g2.mul(g1);
FP4 cpm2 = g2.trace();
c = c.xtr_pow2(cp,cpm1,cpm2,a,b);
c.geta().A.toBytes(t);
H.process_array(t);
c.geta().B.toBytes(t);
H.process_array(t);
c.getb().A.toBytes(t);
H.process_array(t);
c.getb().B.toBytes(t);
H.process_array(t);
W.X.toBytes(t);
H.process_array(t);
W.Y.toBytes(t);
H.process_array(t);
t = H.hash();
for (int i = 0;i < PAS;i++)
{
CK[i] = t[i];
}
return 0;
}
/* calculate common key on server side */
/* Z=r.A - no time permits involved */
public static int SERVER_KEY(sbyte[] Z, sbyte[] SST, sbyte[] W, sbyte[] xID, sbyte[] xCID, sbyte[] SK)
{
HASH H = new HASH();
sbyte[] t = new sbyte[EFS];
ECP2 sQ = ECP2.fromBytes(SST);
if (sQ.is_infinity())
{
return INVALID_POINT;
}
ECP R = ECP.fromBytes(Z);
if (R.is_infinity())
{
return INVALID_POINT;
}
ECP U;
if (xCID != null)
{
U = ECP.fromBytes(xCID);
}
else
{
U = ECP.fromBytes(xID);
}
if (U.is_infinity())
{
return INVALID_POINT;
}
BIG w = BIG.fromBytes(W);
U = PAIR.G1mul(U,w);
FP12 g = PAIR.ate(sQ,R);
g = PAIR.fexp(g);
FP4 c = g.trace();
c.geta().A.toBytes(t);
H.process_array(t);
c.geta().B.toBytes(t);
H.process_array(t);
c.getb().A.toBytes(t);
H.process_array(t);
c.getb().B.toBytes(t);
H.process_array(t);
U.X.toBytes(t);
H.process_array(t);
U.Y.toBytes(t);
H.process_array(t);
t = H.hash();
for (int i = 0;i < PAS;i++)
{
SK[i] = t[i];
}
return 0;
}
/* return time since epoch */
public static int GET_TIME()
{
DateTime date = DateTime.Now;
return (int)(date.Ticks / 1000);
}
/* Generate Y = H(epoch, xCID/xID) */
public static void GET_Y(int TimeValue, sbyte[] xCID, sbyte[] Y)
{
sbyte[] h = hashit(TimeValue,xCID);
BIG y = BIG.fromBytes(h);
BIG q = new BIG(ROM.CURVE_Order);
y.mod(q);
y.toBytes(Y);
}
/* One pass MPIN Client */
public static int CLIENT(int date, sbyte[] CLIENT_ID, RAND RNG, sbyte[] X, int pin, sbyte[] TOKEN, sbyte[] SEC, sbyte[] xID, sbyte[] xCID, sbyte[] PERMIT, int TimeValue, sbyte[] Y)
{
int rtn = 0;
sbyte[] pID;
if (date == 0)
{
pID = xID;
}
else
{
pID = xCID;
}
rtn = CLIENT_1(date,CLIENT_ID,RNG,X,pin,TOKEN,SEC,xID,xCID,PERMIT);
if (rtn != 0)
{
return rtn;
}
GET_Y(TimeValue,pID,Y);
rtn = CLIENT_2(X,Y,SEC);
if (rtn != 0)
{
return rtn;
}
return 0;
}
/* One pass MPIN Server */
public static int SERVER(int date, sbyte[] HID, sbyte[] HTID, sbyte[] Y, sbyte[] SST, sbyte[] xID, sbyte[] xCID, sbyte[] SEC, sbyte[] E, sbyte[] F, sbyte[] CID, int TimeValue)
{
int rtn = 0;
sbyte[] pID;
if (date == 0)
{
pID = xID;
}
else
{
pID = xCID;
}
SERVER_1(date,CID,HID,HTID);
GET_Y(TimeValue,pID,Y);
rtn = SERVER_2(date,HID,HTID,Y,SST,xID,xCID,SEC,E,F);
if (rtn != 0)
{
return rtn;
}
return 0;
}
}