blob: 4dde8c6dc2ab8a95f5d5d39d8a85f9b877a62e19 [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 */
package org.apache.milagro.amcl.BLS383;
import java.util.Date;
import org.apache.milagro.amcl.RAND;
import org.apache.milagro.amcl.HASH256;
import org.apache.milagro.amcl.HASH384;
import org.apache.milagro.amcl.HASH512;
import org.apache.milagro.amcl.AES;
public class MPIN
{
// public static final int SHA256=32;
// public static final int SHA384=48;
// public static final int SHA512=64;
public static final int EFS=BIG.MODBYTES;
public static final int EGS=BIG.MODBYTES;
// public static final int PAS=16;
public static final int INVALID_POINT=-14;
public static final int BAD_PARAMS=-11;
public static final int WRONG_ORDER=-18;
public static final int BAD_PIN=-19;
/* Configure your PIN here */
public static final int MAXPIN=10000; /* PIN less than this */
public static final int PBLEN=14; /* Number of bits in PIN */
public static final int TS=10; /* 10 for 4 digit PIN, 14 for 6-digit PIN - 2^TS/TS approx = sqrt(MAXPIN) */
public static final int TRAP=200; /* 200 for 4 digit PIN, 2000 for 6-digit PIN - approx 2*sqrt(MAXPIN) */
// public static final int HASH_TYPE=SHA256;
/* Hash number (optional) and string to array size of Bigs */
public static byte[] hashit(int sha,int n,byte[] B,int len)
{
byte[] R=null;
if (sha==ECP.SHA256)
{
HASH256 H=new HASH256();
if (n>0) H.process_num(n);
H.process_array(B);
R=H.hash();
}
if (sha==ECP.SHA384)
{
HASH384 H=new HASH384();
if (n>0) H.process_num(n);
H.process_array(B);
R=H.hash();
}
if (sha==ECP.SHA512)
{
HASH512 H=new HASH512();
if (n>0) H.process_num(n);
H.process_array(B);
R=H.hash();
}
if (R==null) return null;
byte[] W=new byte[len];
if (sha>=len)
for (int i=0;i<len;i++) W[i]=R[i];
else
{
for (int i=0;i<sha;i++) W[i+len-sha]=R[i];
for (int i=0;i<len-sha;i++) W[i]=0;
//for (int i=0;i<sha;i++) W[i]=R[i];
//for (int i=sha;i<len;i++) W[i]=0;
}
return W;
}
/* return time in slots since epoch */
public static int today() {
Date date=new Date();
return (int) (date.getTime()/(1000*60*1440));
}
public static byte[] HASH_ID(int sha,byte[] ID,int len)
{
return hashit(sha,0,ID,len);
}
/* Hash the M-Pin transcript - new */
public static byte[] HASH_ALL(int sha,byte[] HID,byte[] xID,byte[] xCID,byte[] SEC,byte[] Y,byte[] R,byte[] W,int len)
{
int i,ilen,tlen=0;
ilen=HID.length+SEC.length+Y.length+R.length+W.length;
if (xCID!=null) ilen+=xCID.length;
else ilen+=xID.length;
byte[] T = new byte[ilen];
for (i=0;i<HID.length;i++) T[i]=HID[i];
tlen+=HID.length;
if (xCID!=null)
{
for (i=0;i<xCID.length;i++) T[i+tlen]=xCID[i];
tlen+=xCID.length;
}
else
{
for (i=0;i<xID.length;i++) T[i+tlen]=xID[i];
tlen+=xID.length;
}
for (i=0;i<SEC.length;i++) T[i+tlen]=SEC[i];
tlen+=SEC.length;
for (i=0;i<Y.length;i++) T[i+tlen]=Y[i];
tlen+=Y.length;
for (i=0;i<R.length;i++) T[i+tlen]=R[i];
tlen+=R.length;
for (i=0;i<W.length;i++) T[i+tlen]=W[i];
tlen+=W.length;
return hashit(sha,0,T,len);
}
/* return time since epoch */
public static int GET_TIME() {
Date date=new Date();
return (int) (date.getTime()/1000);
}
public static byte[] mpin_hash(int sha,FP4 c,ECP U)
{
byte[] w=new byte[EFS];
byte[] t=new byte[6*EFS];
byte[] h=null;
c.geta().getA().toBytes(w); for (int i=0;i<EFS;i++) t[i]=w[i];
c.geta().getB().toBytes(w); for (int i=EFS;i<2*EFS;i++) t[i]=w[i-EFS];
c.getb().getA().toBytes(w); for (int i=2*EFS;i<3*EFS;i++) t[i]=w[i-2*EFS];
c.getb().getB().toBytes(w); for (int i=3*EFS;i<4*EFS;i++) t[i]=w[i-3*EFS];
U.getX().toBytes(w); for (int i=4*EFS;i<5*EFS;i++) t[i]=w[i-4*EFS];
U.getY().toBytes(w); for (int i=5*EFS;i<6*EFS;i++) t[i]=w[i-5*EFS];
if (sha==ECP.SHA256)
{
HASH256 H=new HASH256();
H.process_array(t);
h=H.hash();
}
if (sha==ECP.SHA384)
{
HASH384 H=new HASH384();
H.process_array(t);
h=H.hash();
}
if (sha==ECP.SHA512)
{
HASH512 H=new HASH512();
H.process_array(t);
h=H.hash();
}
if (h==null) return null;
byte[] R=new byte[ECP.AESKEY];
for (int i=0;i<ECP.AESKEY;i++) R[i]=h[i];
return R;
}
/* 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.getS();
ECP R;
int r=0;
BIG x=P.getX();
u.copy(x);
while (true)
{
u.dec(1); u.norm();
r++;
R=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 */
public static int ENCODING(RAND rng,byte[] E)
{
int rn,m,su,sv;
byte[] T=new byte[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.getByte(); /*if (su<0) su=-su;*/ su%=2;
ECP W=map(u,su);
P.sub(W); //P.affine();
sv=P.getS();
rn=unmap(v,P);
m=rng.getByte(); /*if (m<0) m=-m;*/ m%=rn;
v.inc(m+1);
E[0]=(byte)(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(byte[] D)
{
int su,sv;
byte[] T=new byte[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); //P.affine();
u=P.getX();
v=P.getY();
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(byte[] R1,byte[] R2,byte[] 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.affine();
P.toBytes(R,false);
return 0;
}
/* W=W1+W2 in group G2 */
public static int RECOMBINE_G2(byte[] W1,byte[] W2,byte[] 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.affine();
P.toBytes(W);
return 0;
}
/* create random secret S */
public static int RANDOM_GENERATE(RAND rng,byte[] S)
{
BIG s;
BIG r=new BIG(ROM.CURVE_Order);
s=BIG.randomnum(r,rng);
//if (ROM.AES_S>0)
//{
// s.mod2m(2*ROM.AES_S);
//}
s.toBytes(S);
return 0;
}
/* Extract PIN from TOKEN for identity CID */
public static int EXTRACT_PIN(int sha,byte[] CID,int pin,byte[] TOKEN)
{
ECP P=ECP.fromBytes(TOKEN);
if (P.is_infinity()) return INVALID_POINT;
byte[] h=hashit(sha,0,CID,EFS);
ECP R=ECP.mapit(h);
pin%=MAXPIN;
R=R.pinmul(pin,PBLEN);
P.sub(R); //P.affine();
P.toBytes(TOKEN,false);
return 0;
}
/* Implement step 2 on client side of MPin protocol */
public static int CLIENT_2(byte[] X,byte[] Y,byte[] 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);
P=PAIR.G1mul(P,px);
P.neg();
P.toBytes(SEC,false);
return 0;
}
/* Implement step 1 on client side of MPin protocol */
public static int CLIENT_1(int sha,int date,byte[] CLIENT_ID,RAND rng,byte[] X,int pin,byte[] TOKEN,byte[] SEC,byte[] xID,byte[] xCID,byte[] PERMIT)
{
BIG r=new BIG(ROM.CURVE_Order);
BIG x;
if (rng!=null)
{
x=BIG.randomnum(r,rng);
//if (ROM.AES_S>0)
//{
// x.mod2m(2*ROM.AES_S);
//}
x.toBytes(X);
}
else
{
x=BIG.fromBytes(X);
}
ECP P,T,W;
BIG px;
// byte[] t=new byte[EFS];
byte[] h=hashit(sha,0,CLIENT_ID,EFS);
P=ECP.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(sha,date,h,EFS);
W=ECP.mapit(h);
if (xID!=null)
{
P=PAIR.G1mul(P,x);
P.toBytes(xID,false);
W=PAIR.G1mul(W,x);
P.add(W);
//P.affine();
}
else
{
P.add(W); //P.affine();
P=PAIR.G1mul(P,x);
}
if (xCID!=null) P.toBytes(xCID,false);
}
else
{
if (xID!=null)
{
P=PAIR.G1mul(P,x);
P.toBytes(xID,false);
}
}
//T.affine();
T.toBytes(SEC,false);
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(byte[] S,byte[] SST)
{
ECP2 Q=ECP2.generator();
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,byte[] X,byte[] G,byte[] W)
{
BIG x;
BIG r=new BIG(ROM.CURVE_Order);
if (rng!=null)
{
x=BIG.randomnum(r,rng);
//if (ROM.AES_S>0)
//{
// x.mod2m(2*ROM.AES_S);
//}
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=ECP.mapit(G);
PAIR.G1mul(P,x).toBytes(W,false);
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(byte[] S,byte[] CID,byte[] 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 sha,int date,byte[] S,byte[] CID,byte[] CTT)
{
byte[] h=hashit(sha,date,CID,EFS);
ECP P=ECP.mapit(h);
BIG s=BIG.fromBytes(S);
ECP OP=PAIR.G1mul(P,s);
OP.toBytes(CTT,false);
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 sha,int date,byte[] CID,byte[] HID,byte[] HTID)
{
byte[] h=hashit(sha,0,CID,EFS);
ECP R,P=ECP.mapit(h);
P.toBytes(HID,false); // new
if (date!=0)
{
// if (HID!=null) P.toBytes(HID);
h=hashit(sha,date,h,EFS);
R=ECP.mapit(h);
P.add(R); //P.affine();
P.toBytes(HTID,false);
}
// else P.toBytes(HID,false);
}
/* Implement step 2 of MPin protocol on server side */
public static int SERVER_2(int date,byte[] HID,byte[] HTID,byte[] Y,byte[] SST,byte[] xID,byte[] xCID,byte[] mSEC,byte[] E,byte[] F)
{
BIG q=new BIG(ROM.Modulus);
ECP2 Q=ECP2.generator();
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); //P.affine();
R=ECP.fromBytes(mSEC);
if (R.is_infinity()) return INVALID_POINT;
FP12 g;
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); //P.affine();
}
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(byte[] E,byte[] 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().getA().lastbits(20)%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().getA().lastbits(20)%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(byte[] TOKEN,byte[] CID,byte[] G1,byte[] G2)
{
ECP P,T;
FP12 g;
T=ECP.fromBytes(TOKEN);
if (T.is_infinity()) return INVALID_POINT;
P=ECP.mapit(CID);
ECP2 Q=ECP2.generator();
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(int sha,byte[] G1,byte[] G2,int pin,byte[] R,byte[] X,byte[] H,byte[] wCID,byte[] CK)
{
byte[] t;
FP12 g1=FP12.fromBytes(G1);
FP12 g2=FP12.fromBytes(G2);
BIG z=BIG.fromBytes(R);
BIG x=BIG.fromBytes(X);
BIG h=BIG.fromBytes(H);
ECP W=ECP.fromBytes(wCID);
if (W.is_infinity()) return INVALID_POINT;
W=PAIR.G1mul(W,x);
// FP2 f=new FP2(new BIG(ROM.Fra),new BIG(ROM.Frb));
BIG r=new BIG(ROM.CURVE_Order);
// BIG q=new BIG(ROM.Modulus);
z.add(h); //new
z.mod(r);
g2.pinpow(pin,PBLEN);
g1.mul(g2);
FP4 c=g1.compow(z,r);
/*
BIG m=new BIG(q);
m.mod(r);
BIG a=new BIG(z);
a.mod(m);
BIG b=new BIG(z);
b.div(m);
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);
*/
t=mpin_hash(sha,c,W);
for (int i=0;i<ECP.AESKEY;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(int sha,byte[] Z,byte[] SST,byte[] W,byte[] H,byte[] HID,byte[] xID,byte[] xCID,byte[] SK)
{
byte[] t;
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 A=ECP.fromBytes(HID);
if (A.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);
BIG h=BIG.fromBytes(H);
A=PAIR.G1mul(A,h); // new
R.add(A); //R.affine();
U=PAIR.G1mul(U,w);
FP12 g=PAIR.ate(sQ,R);
g=PAIR.fexp(g);
FP4 c=g.trace();
t=mpin_hash(sha,c,U);
for (int i=0;i<ECP.AESKEY;i++) SK[i]=t[i];
return 0;
}
/* Generate Y = H(epoch, xCID/xID) */
public static void GET_Y(int sha,int TimeValue,byte[] xCID,byte[] Y)
{
byte[] h = hashit(sha,TimeValue,xCID,EFS);
BIG y = BIG.fromBytes(h);
BIG q=new BIG(ROM.CURVE_Order);
y.mod(q);
//if (ROM.AES_S>0)
//{
// y.mod2m(2*ROM.AES_S);
//}
y.toBytes(Y);
}
/* One pass MPIN Client */
public static int CLIENT(int sha,int date,byte[] CLIENT_ID,RAND RNG,byte[] X,int pin,byte[] TOKEN,byte[] SEC,byte[] xID,byte[] xCID,byte[] PERMIT, int TimeValue, byte[] Y)
{
int rtn=0;
byte[] pID;
if (date == 0)
pID = xID;
else
pID = xCID;
rtn = CLIENT_1(sha,date,CLIENT_ID,RNG,X,pin,TOKEN,SEC,xID,xCID,PERMIT);
if (rtn != 0)
return rtn;
GET_Y(sha,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 sha,int date,byte[] HID,byte[] HTID,byte[] Y,byte[] SST,byte[] xID,byte[] xCID,byte[] SEC,byte[] E,byte[] F,byte[] CID, int TimeValue)
{
int rtn=0;
byte[] pID;
if (date == 0)
pID = xID;
else
pID = xCID;
SERVER_1(sha,date,CID,HID,HTID);
GET_Y(sha,TimeValue,pID,Y);
rtn = SERVER_2(date,HID,HTID,Y,SST,xID,xCID,SEC,E,F);
if (rtn != 0)
return rtn;
return 0;
}
}