python/mpin_ZZZ.py.in (1,303 lines of code) (raw):

#!/usr/bin/env python3 """ 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 This module use cffi to access the c functions in the mpin library. There is also an example usage program in this file. """ import cffi import platform import os ffi = cffi.FFI() ffi.cdef(""" typedef struct { unsigned int ira[21]; /* random number... */ int rndptr; /* ...array & pointer */ unsigned int borrow; int pool_ptr; char pool[32]; /* random pool */ } csprng; typedef struct { int len; int max; char *val; } octet; extern void CREATE_CSPRNG(csprng *R,octet *S); extern void KILL_CSPRNG(csprng *R); extern void OCT_clear(octet *O); extern void HASH_ID(int h,octet *ID,octet *HID); extern unsigned int GET_TIME(void); extern void MPIN_ZZZ_GET_Y(int h,int t,octet *O,octet *Y); extern int MPIN_ZZZ_EXTRACT_PIN(int h,octet *ID,int pin,octet *CS); extern int MPIN_ZZZ_CLIENT(int h,int d,octet *ID,csprng *R,octet *x,int pin,octet *T,octet *V,octet *U,octet *UT,octet *TP, octet* MESSAGE, int t, octet *y); extern int MPIN_ZZZ_CLIENT_1(int h,int d,octet *ID,csprng *R,octet *x,int pin,octet *T,octet *S,octet *U,octet *UT,octet *TP); extern int MPIN_ZZZ_RANDOM_GENERATE(csprng *R,octet *S); extern int MPIN_ZZZ_GET_DVS_KEYPAIR(csprng *R,octet *Z,octet *Pa); extern int MPIN_ZZZ_CLIENT_2(octet *x,octet *y,octet *V); extern int MPIN_ZZZ_SERVER(int h,int d,octet *HID,octet *HTID,octet *y,octet *SS,octet *U,octet *UT,octet *V,octet *E,octet *F,octet *ID,octet *MESSAGE, int t,octet *Pa); extern void MPIN_ZZZ_SERVER_1(int h,int d,octet *ID,octet *HID,octet *HTID); extern int MPIN_ZZZ_SERVER_2(int d,octet *HID,octet *HTID,octet *y,octet *SS,octet *U,octet *UT,octet *V,octet *E,octet *F,octet *Pa); extern int MPIN_ZZZ_RECOMBINE_G1(octet *Q1,octet *Q2,octet *Q); extern int MPIN_ZZZ_RECOMBINE_G2(octet *P1,octet *P2,octet *P); extern int MPIN_ZZZ_KANGAROO(octet *E,octet *F); extern int MPIN_ZZZ_ENCODING(csprng *R,octet *TP); extern int MPIN_ZZZ_DECODING(octet *TP); extern unsigned int today(void); extern int MPIN_ZZZ_GET_G1_MULTIPLE(csprng *R,int type,octet *x,octet *G,octet *W); extern int MPIN_ZZZ_GET_G2_MULTIPLE(csprng *R,int type,octet *x,octet *G,octet *W); extern void HASH_ALL(int h,octet *I,octet *U,octet *CU,octet *Y,octet *V,octet *R,octet *W,octet *H); extern int MPIN_ZZZ_GET_CLIENT_SECRET(octet *S,octet *ID,octet *CS); extern int MPIN_ZZZ_GET_CLIENT_PERMIT(int h,int d,octet *S,octet *ID,octet *TP); extern int MPIN_ZZZ_GET_SERVER_SECRET(octet *S,octet *SS); extern int MPIN_ZZZ_PRECOMPUTE(octet *T,octet *ID,octet *CP,octet *g1,octet *g2); extern int MPIN_ZZZ_SERVER_KEY(int h,octet *Z,octet *SS,octet *w,octet *p,octet *I,octet *U,octet *UT,octet *K); extern int MPIN_ZZZ_CLIENT_KEY(int h,octet *g1,octet *g2,int pin,octet *r,octet *x,octet *p,octet *T,octet *K); extern void AES_GCM_ENCRYPT(octet *K,octet *IV,octet *H,octet *P,octet *C,octet *T); extern void AES_GCM_DECRYPT(octet *K,octet *IV,octet *H,octet *C,octet *P,octet *T); extern void hex2bytes(char *hex, char *bin); extern void generateRandom(csprng*, octet*); extern int generateOTP(csprng*); """) if (platform.system() == 'Windows'): libamcl_mpin_ZZZ = ffi.dlopen("libamcl_mpin_ZZZ.dll") libamcl_core = ffi.dlopen("libamcl_core.dll") elif (platform.system() == 'Darwin'): libamcl_mpin_ZZZ = ffi.dlopen("libamcl_mpin_ZZZ.dylib") libamcl_core = ffi.dlopen("libamcl_core.dylib") else: libamcl_mpin_ZZZ = ffi.dlopen("libamcl_mpin_ZZZ.so") libamcl_core = ffi.dlopen("libamcl_core.so") # Group Size PGS = @NB@ # Field Size PFS = @NB@ CURVE_SECURITY = @CS@ if CURVE_SECURITY == 128: G2 = 4 * PFS HASH_TYPE_ZZZ = 32 AESKEY_ZZZ = 16 if CURVE_SECURITY == 192: G2 = 8 * PFS HASH_TYPE_ZZZ = 48 AESKEY_ZZZ = 24 if CURVE_SECURITY == 256: G2 = 16 * PFS HASH_TYPE_ZZZ = 64 AESKEY_ZZZ = 32 G1 = 2 * PFS + 1 GT = 3 * G2 # AES-GCM IV length IVL = 12 def to_str(octet_value): """Converts an octet type into a string Add all the values in an octet into an array. Args:: octet_value. An octet pointer type Returns:: String Raises: Exception """ i = 0 val = [] while i < octet_value.len: val.append(octet_value.val[i]) i = i + 1 out = b'' for x in val: out = out + x return out def make_octet(length, value=None): """Generates an octet pointer Generates an empty octet or one filled with the input value Args:: length: Length of empty octet value: Data to assign to octet Returns:: oct_ptr: octet pointer val: data associated with octet to prevent garbage collection Raises: """ oct_ptr = ffi.new("octet*") if value: val = ffi.new("char [%s]" % len(value), value) oct_ptr.val = val oct_ptr.max = len(value) oct_ptr.len = len(value) else: val = ffi.new("char []", length) oct_ptr.val = val oct_ptr.max = length oct_ptr.len = length return oct_ptr, val def today(): """Today's date as days elapsed from the epoch Today's date as days elapsed from the epoch. This function uses the system clock Args:: Returns:: epoch_date: epoch days Raises: """ return libamcl_mpin_ZZZ.today() def get_time(): """Get time elapsed from the epoch Time elapsed from the epoch. This function uses the system clock Args:: Returns:: epoch_time: epoch time Raises: """ return libamcl_mpin_ZZZ.GET_TIME() def create_csprng(seed): """Make a Cryptographically secure pseudo-random number generator instance Make a Cryptographically secure pseudo-random number generator instance Args:: seed: random seed value Returns:: rng: Pointer to cryptographically secure pseudo-random number generator instance Raises: """ seed_oct, seed_val = make_octet(None, seed) # random number generator rng = ffi.new('csprng*') libamcl_core.CREATE_CSPRNG(rng, seed_oct) libamcl_core.OCT_clear(seed_oct) return rng def kill_csprng(rng): """Kill a random number generator Deletes all internal state Args:: rng: Pointer to cryptographically secure pseudo-random number generator instance Returns:: Raises: """ libamcl_core.KILL_CSPRNG(rng) return 0 def hash_id(hash_type, mpin_id): """Hash an M-Pin Identity to an octet Hash an M-Pin Identity to an octet Args:: mpin_id: An octet pointer containing the M-Pin ID Returns:: hash_mpin_id: hash of the M-Pin ID Raises: """ # Hash value of mpin_id mpin_id1, mpin_id1_val = make_octet(None, mpin_id) hash_mpin_id1, hash_mpin_id1_val = make_octet(PFS) libamcl_core.HASH_ID(hash_type, mpin_id1, hash_mpin_id1) hash_mpin_id = to_str(hash_mpin_id1) # clear memory libamcl_core.OCT_clear(mpin_id1) libamcl_core.OCT_clear(hash_mpin_id1) return hash_mpin_id def random_generate(rng): """Generate a random group element Generate a random group element Args:: rng: Pointer to cryptographically secure pseudo-random number generator instance Returns:: error_code: error from the C function s: random group element Raises: """ s1, s_val = make_octet(PGS) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_RANDOM_GENERATE(rng, s1) s = to_str(s1) # clear memory libamcl_core.OCT_clear(s1) return error_code, s def get_dvs_keypair(rng): """Create a public key in G2 for thee client Create a public in G2 for the client Args:: rng: a random number generator Returns:: error_code: error from the C function z: private key pa: public key Raises: """ pa, pa_val = make_octet(G2) z, z_val = make_octet(PFS) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_GET_DVS_KEYPAIR(rng, z, pa) pa = to_str(pa) z = to_str(z) # clear memory libamcl_core.OCT_clear(z) libamcl_core.OCT_clear(pa) return error_code, z, pa def get_server_secret(master_secret): """Create a server secret in G2 from a master secret Create a server secret in G2 from a master secret Args:: master_secret: An octet pointer to the master secret Returns:: error_code: error from the C function server_secret: Server secret Raises: """ master_secret1, master_secret1_val = make_octet(None, master_secret) server_secret1, server_secret1_val = make_octet(G2) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_GET_SERVER_SECRET( master_secret1, server_secret1) server_secret = to_str(server_secret1) # clear memory libamcl_core.OCT_clear(master_secret1) libamcl_core.OCT_clear(server_secret1) return error_code, server_secret def recombine_G2(W1, W2): """Add two members from the group G2 Add two members from the group G2 Args:: W1: An input member of G2 W2: An input member of G2 Returns:: error_code: error from the C function W: An output member of G1; W = W1+W2 Raises: """ w11, w11_val = make_octet(None, W1) w21, w21_val = make_octet(None, W2) w1, w1_val = make_octet(G2) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_RECOMBINE_G2(w11, w21, w1) w = to_str(w1) # clear memory libamcl_core.OCT_clear(w11) libamcl_core.OCT_clear(w21) libamcl_core.OCT_clear(w1) return error_code, w def get_client_secret(master_secret, hash_mpin_id): """Create a client secret in G1 from a master secret and the hash of the M-Pin Id Create a client secret in G1 from a master secret and the hash of the M-Pin Id Args:: master_secret: An octet pointer to the master secret hash_mpin_id: An octet pointer to the hash of the M-Pin ID Returns:: error_code: error from the C function client_secret: Client secret Raises: """ master_secret1, master_secret1_val = make_octet(None, master_secret) hash_mpin_id1, hash_mpin_id1_val = make_octet(None, hash_mpin_id) client_secret1, client_secret1_val = make_octet(G1) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_GET_CLIENT_SECRET( master_secret1, hash_mpin_id1, client_secret1) client_secret = to_str(client_secret1) # clear memory libamcl_core.OCT_clear(master_secret1) libamcl_core.OCT_clear(hash_mpin_id1) libamcl_core.OCT_clear(client_secret1) return error_code, client_secret def recombine_G1(q1, q2): """Add two members from the group G1 Add two members from the group G1 Args:: q1: An input member of G1 q2: An input member of G1 Returns:: error_code: error from the C function q: An output member of G1 = Q1+Q2 Raises: """ q11, q11_val = make_octet(None, q1) q21, q21_val = make_octet(None, q2) q1, q1_val = make_octet(G1) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_RECOMBINE_G1(q11, q21, q1) q = to_str(q1) # clear memory libamcl_core.OCT_clear(q11) libamcl_core.OCT_clear(q21) libamcl_core.OCT_clear(q1) return error_code, q def get_client_permit(hash_type, epoch_date, master_secret, hash_mpin_id): """Create a time permit in G1 from a master secret, hash of the M-Pin Id and epoch days Create a time permit in G1 from a master secret, hash of the M-Pin Id and epoch days Args:: epoch_date: Epoch days master_secret: An octet pointer to the master secret hash_mpin_id: An octet pointer to the hash of the M-Pin ID Returns:: error_code: error from the C function time_permit: Time permit Raises: """ master_secret1, master_secret1_val = make_octet(None, master_secret) hash_mpin_id1, hash_mpin_id1_val = make_octet(None, hash_mpin_id) time_permit1, time_permit1_val = make_octet(G1) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_GET_CLIENT_PERMIT( hash_type, epoch_date, master_secret1, hash_mpin_id1, time_permit1) time_permit = to_str(time_permit1) # clear memory libamcl_core.OCT_clear(master_secret1) libamcl_core.OCT_clear(hash_mpin_id1) libamcl_core.OCT_clear(time_permit1) return error_code, time_permit def extract_pin(hash_type, mpin_id, pin, client_secret): """Extract a PIN from client secret Extract a PIN from client secret Args:: mpin_id: M-Pin ID pin: PIN input by user client_secret: User's client secret Returns:: error_code: error from the C function token: Result of extracting a PIN from client secret Raises: """ mpin_id1, mpin_id1_val = make_octet(None, mpin_id) client_secret1, client_secret1_val = make_octet(None, client_secret) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_EXTRACT_PIN( hash_type, mpin_id1, pin, client_secret1) client_secret = to_str(client_secret1) # clear memory libamcl_core.OCT_clear(mpin_id1) libamcl_core.OCT_clear(client_secret1) return error_code, client_secret def precompute(token, hash_mpin_id): """Precompute values for use by the client side of M-Pin Full Precompute values for use by the client side of M-Pin Full Args:: token: M-Pin token hash_mpin_id: hash of the M-Pin ID Returns:: error_code: error from the C function pc1: Precomputed value one pc2: Precomputed value two Raises: """ token1, token1_val = make_octet(None, token) hash_mpin_id1, hash_mpin_id1_val = make_octet(None, hash_mpin_id) pc11, pc11_val = make_octet(GT) pc21, pc21_val = make_octet(GT) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_PRECOMPUTE( token1, hash_mpin_id1, ffi.NULL, pc11, pc21) pc1 = to_str(pc11) pc2 = to_str(pc21) # clear memory libamcl_core.OCT_clear(token1) libamcl_core.OCT_clear(hash_mpin_id1) libamcl_core.OCT_clear(pc11) libamcl_core.OCT_clear(pc21) return error_code, pc1, pc2 def client_1(hash_type, epoch_date, mpin_id, rng, x, pin, token, time_permit): """Perform first pass of the client side of the three pass version of the M-Pin protocol Perform first pass of the client side of the three pass version of the M-Pin protocol. If Time Permits are disabled then set epoch_date = 0.In this case UT is not generated0 and can be set to None. If Time Permits are enabled, and PIN error detection is OFF, U is not generated and can be set to None. If Time Permits are enabled and PIN error detection is ON then U and UT are both generated. Args:: epoch_date: Date, in days since the epoch. Set to 0 if Time permits disabled mpin_id: M-Pin ID rng: cryptographically secure random number generator pin: PIN entered by user token: M-Pin token time_permit: M-Pin time permit Returns:: error_code: error from the C function x: Randomly generated integer if RNG!=None, otherwise must be provided as an input u: u = x.H(ID) ut: ut = x.(H(ID)+H(epoch_date|H(ID))) v: v = CS+TP, where CS is the reconstructed client secret and TP is the time permit Raises: """ mpin_id1, mpin_id1_val = make_octet(None, mpin_id) token1, token1_val = make_octet(None, token) if time_permit: time_permit1, time_permit1_val = make_octet(None, time_permit) else: time_permit1 = ffi.NULL if rng is None: x1, x1_val = make_octet(None, x) rng = ffi.NULL else: x1, x1_val = make_octet(PGS) u1, u1_val = make_octet(G1) ut1, ut1_val = make_octet(G1) v1, v1_val = make_octet(G1) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_CLIENT_1( hash_type, epoch_date, mpin_id1, rng, x1, pin, token1, v1, u1, ut1, time_permit1) x = to_str(x1) u = to_str(u1) ut = to_str(ut1) v = to_str(v1) # clear memory if time_permit: libamcl_core.OCT_clear(time_permit1) libamcl_core.OCT_clear(mpin_id1) libamcl_core.OCT_clear(token1) libamcl_core.OCT_clear(x1) libamcl_core.OCT_clear(u1) libamcl_core.OCT_clear(ut1) libamcl_core.OCT_clear(v1) return error_code, x, u, ut, v def client_2(x, y, sec): """Perform second pass of the client side of the 3-pass version of the M-Pin protocol Perform second pass of the client side of the 3-pass version of the M-Pin protocol Args:: x: locally generated random number y: random challenge from server sec: CS+TP, where CS is the reconstructed client secret and TP is the time permit Returns:: error_code: error from the C function v: v = -(x+y)(CS+TP), where CS is the reconstructed client secret and TP is the time permit Raises: """ x1, x1_val = make_octet(None, x) y1, y1_val = make_octet(None, y) sec1, sec1_val = make_octet(None, sec) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_CLIENT_2(x1, y1, sec1) sec = to_str(sec1) # clear memory libamcl_core.OCT_clear(x1) libamcl_core.OCT_clear(y1) libamcl_core.OCT_clear(sec1) return error_code, sec def client(hash_type, epoch_date, mpin_id, rng, x, pin, token, time_permit, message, epoch_time): """Perform client side of the one-pass version of the M-Pin protocol Perform client side of the one-pass version of the M-Pin protocol. If Time Permits are disabled then set epoch_date = 0.In this case UT is not generated and can be set to None. If Time Permits are enabled, and PIN error detection is OFF, U is not generated and can be set to None. If Time Permits are enabled and PIN error detection is ON then U and UT are both generated. Args:: epoch_date: Date, in days since the epoch. Set to 0 if Time permits disabled mpin_id: M-Pin ID rng: cryptographically secure random number generator pin: PIN entered by user token: M-Pin token time_permit: M-Pin time permit message: message to be signed epoch_time: Epoch time in seconds Returns:: error_code: error from the C function x: Randomly generated integer if RNG!=None, otherwise must be provided as an input u: u = x.H(ID) ut: ut = x.(H(ID)+H(epoch_date|H(ID))) v: v = -(x+y)(CS+TP), where CS is the reconstructed client secret and TP is the time permit y: y = t H(t|U) or y = H(t|UT) if Time Permits enabled Raises: """ mpin_id1, mpin_id1_val = make_octet(None, mpin_id) token1, token1_val = make_octet(None, token) if time_permit: time_permit1, time_permit1_val = make_octet(None, time_permit) else: time_permit1 = ffi.NULL if rng is not None: x1, x1_val = make_octet(PGS) else: x1, x1_val = make_octet(None, x) if message is None: message1 = ffi.NULL else: message1, message1_val = make_octet(None, message) u1, u1_val = make_octet(G1) ut1, ut1_val = make_octet(G1) v1, v1_val = make_octet(G1) y1, y1_val = make_octet(PGS) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_CLIENT( hash_type, epoch_date, mpin_id1, rng, x1, pin, token1, v1, u1, ut1, time_permit1, message1, epoch_time, y1) x = to_str(x1) u = to_str(u1) ut = to_str(ut1) v = to_str(v1) y = to_str(y1) # clear memory if time_permit: libamcl_core.OCT_clear(time_permit1) if message: libamcl_core.OCT_clear(message1) libamcl_core.OCT_clear(mpin_id1) libamcl_core.OCT_clear(token1) libamcl_core.OCT_clear(x1) libamcl_core.OCT_clear(u1) libamcl_core.OCT_clear(ut1) libamcl_core.OCT_clear(v1) libamcl_core.OCT_clear(y1) return error_code, x, u, ut, v, y def get_G1_multiple(rng, type, x, P): """Find a random multiple of a point in G1 Calculate W=x*P where random x < q is the order of the group of points on the curve. When rng is None x is passed in otherwise it is passed out. If type=0 then P is. point on the curve or else P is an octet that has to be mapped to the curve Args:: rng: Pointer to cryptographically secure pseudo-random number generator instance type: determines type of action to be taken P: if type=0 a point in G1, else an octet to be mapped to G1 Returns:: error_code: error from the C function x: an output internally randomly generated if rng!=None, otherwise must be provided as an input W: W = x.P or W = x.M(P), where M(.) is a mapping when type = 0 Raises: """ if rng is not None: x1, x1_val = make_octet(PGS) rng_in = rng else: x1, x1_val = make_octet(None, x) rng_in = ffi.NULL P1, P1_val = make_octet(None, P) W1, W1_val = make_octet(G1) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_GET_G1_MULTIPLE( rng_in, type, x1, P1, W1) x = to_str(x1) W = to_str(W1) # clear memory libamcl_core.OCT_clear(x1) libamcl_core.OCT_clear(P1) libamcl_core.OCT_clear(W1) return error_code, x, W def server_1(hash_type, epoch_date, mpin_id): """Perform first pass of the server side of the 3-pass version of the M-Pin protocol Perform first pass of the server side of the 3-pass version of the M-Pin protocol If Time Permits are disabled, set epoch_date = 0, and UT and HTID are not generated and can be set to None. If Time Permits are enabled, and PIN error detection is OFF, U and HID are not needed and caxn be set to None. If Time Permits are enabled, and PIN error detection is ON, U, UT, HID and HTID are all required. Args:: epoch_date: Date, in days since the epoch. Set to 0 if Time permits disabled mpin_id: M-Pin ID or hash of the M-Pin ID in anonymous mode Returns:: HID: H(mpin_id). H is a map to a point on the curve HTID: H(mpin_id)+H(epoch_date|H(mpin_id)). H is a map to a point on the curve Raises: """ mpin_id1, mpin_id1_val = make_octet(None, mpin_id) HTID1, HTID1_val = make_octet(G1) HID1, HID1_val = make_octet(G1) libamcl_mpin_ZZZ.MPIN_ZZZ_SERVER_1( hash_type, epoch_date, mpin_id1, HID1, HTID1) HID = to_str(HID1) HTID = to_str(HTID1) # clear memory libamcl_core.OCT_clear(mpin_id1) libamcl_core.OCT_clear(HTID1) libamcl_core.OCT_clear(HID1) return HID, HTID def server_2(epoch_date, HID, HTID, y, server_secret, u, ut, v, pa): """Perform third pass on the server side of the 3-pass version of the M-Pin protocol Perform server side of the three-pass version of the M-Pin protocol. If Time Permits are disabled, set epoch_date = 0, and UT and HTID are not generated and can be set to None. If Time Permits are enabled, and PIN error detection is OFF, U and HID are not needed and can be set to None. If Time Permits are enabled, and PIN error detection is ON, U, UT, HID and HTID are all required. Args:: epoch_date: Date, in days since the epoch. Set to 0 if Time permits disabled HID: H(mpin_id). H is a map to a point on the curve HTID: H(mpin_id)+H(epoch_date|H(mpin_id)). H is a map to a point on the curve y: locally generated random number server_secret: Server secret u: u = x.H(ID) ut: ut = x.(H(ID)+H(epoch_date|H(ID))) v: v = -(x+y)(CS+TP), where CS is the reconstructed client secret and TP is the time permit pa: client's public key for key-escrow-less Returns:: error_code: error from the C function e: value to help the Kangaroos to find the PIN error, or None if not required f: value to help the Kangaroos to find the PIN error, or None if not required Raises: """ HID1, HID1_val = make_octet(None, HID) HTID1, HTID1_val = make_octet(None, HTID) y1, y1_val = make_octet(None, y) server_secret1, server_secret1_val = make_octet(None, server_secret) u1, u1_val = make_octet(None, u) ut1, ut1_val = make_octet(None, ut) v1, v1_val = make_octet(None, v) if pa is None: pa1 = ffi.NULL else: pa1, pa1_val = make_octet(None, pa) e1, e1_val = make_octet(GT) f1, f1_val = make_octet(GT) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_SERVER_2( epoch_date, HID1, HTID1, y1, server_secret1, u1, ut1, v1, e1, f1, pa1) e = to_str(e1) f = to_str(f1) # clear memory libamcl_core.OCT_clear(HID1) libamcl_core.OCT_clear(HTID1) libamcl_core.OCT_clear(y1) libamcl_core.OCT_clear(server_secret1) libamcl_core.OCT_clear(u1) libamcl_core.OCT_clear(ut1) libamcl_core.OCT_clear(v1) libamcl_core.OCT_clear(e1) libamcl_core.OCT_clear(f1) return error_code, e, f def server( hash_type, epoch_date, server_secret, u, ut, v, mpin_id, message, epoch_time, pa): """Perform server side of the one-pass version of the M-Pin protocol Perform server side of the one-pass version of the M-Pin protocol. If Time Permits are disabled, set epoch_date = 0, and UT and HTID are not generated and can be set to None. If Time Permits are enabled, and PIN error detection is OFF, U and HID are not needed and can be set to None. If Time Permits are enabled, and PIN error detection is ON, U, UT, HID and HTID are all required. Args:: epoch_date: Date, in days since the epoch. Set to 0 if Time permits disabled server_secret: Server secret u: u = x.H(ID) ut: ut = x.(H(ID)+H(epoch_date|H(ID))) v: v = -(x+y)(CS+TP), where CS is the reconstructed client secret and TP is the time permit mpin_id: M-Pin ID or hash of the M-Pin ID in anonymous mode message: message to be signed epoch_time: Epoch time in seconds pa: client's public key for key-escrow-less Returns:: error_code: error from the C function HID: H(mpin_id). H is a map to a point on the curve HTID: H(mpin_id)+H(epoch_date|H(mpin_id)). H is a map to a point on the curve e: value to help the Kangaroos to find the PIN error, or None if not required f: value to help the Kangaroos to find the PIN error, or None if not required y: y = t H(t|U) or y = H(t|UT) if Time Permits enabled used for debug Raises: """ if message is None: message1 = ffi.NULL else: message1, message1_val = make_octet(None, message) server_secret1, server_secret1_val = make_octet(None, server_secret) u1, u1_val = make_octet(None, u) ut1, ut1_val = make_octet(None, ut) v1, v1_val = make_octet(None, v) mpin_id1, mpin_id1_val = make_octet(None, mpin_id) HTID1, HTID1_val = make_octet(G1) HID1, HID1_val = make_octet(G1) e1, e1_val = make_octet(GT) f1, f1_val = make_octet(GT) y1, y1_val = make_octet(PGS) if pa is None: pa1 = ffi.NULL else: pa1, pa1_val = make_octet(None, pa) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_SERVER( hash_type, epoch_date, HID1, HTID1, y1, server_secret1, u1, ut1, v1, e1, f1, mpin_id1, message1, epoch_time, pa1) HID = to_str(HID1) HTID = to_str(HTID1) e = to_str(e1) f = to_str(f1) y = to_str(y1) # clear memory if message: libamcl_core.OCT_clear(message1) libamcl_core.OCT_clear(server_secret1) libamcl_core.OCT_clear(u1) libamcl_core.OCT_clear(ut1) libamcl_core.OCT_clear(mpin_id1) libamcl_core.OCT_clear(v1) libamcl_core.OCT_clear(HID1) libamcl_core.OCT_clear(HTID1) libamcl_core.OCT_clear(e1) libamcl_core.OCT_clear(f1) libamcl_core.OCT_clear(y1) return error_code, HID, HTID, e, f, y def kangaroo(e, f): """Use Pollards Kangaroos to find PIN error Use Pollards Kangaroos to find PIN error Args:: e: a member of the group GT f: a member of the group GT = E^pin_error Returns:: pin_error: error in PIN or 0 if Kangaroos failed Raises: """ e1, e1_val = make_octet(None, e) f1, f1_val = make_octet(None, f) # clear memory libamcl_core.OCT_clear(e1) libamcl_core.OCT_clear(f1) pin_error = libamcl_mpin_ZZZ.MPIN_ZZZ_KANGAROO(e1, f1) return pin_error def hash_all(hash_type, hash_mpin_id, u, ut, v, y, z, t): """Hash the session transcript Hash the session transcript Args:: hash_mpin_id: An octet pointer to the hash of the M-Pin ID u: u = x.H(mpin_id) ut: ut = x.(H(ID)+H(epoch_date|H(ID))) v: v = -(x+y)(CS+TP), where CS is the reconstructed client secret and TP is the time permit y: server challenge z: client part response t: server part response Returns:: hm: hash of the input values Raises: """ if ut is None: ut1 = ffi.NULL else: ut1, ut1_val = make_octet(None, ut) hash_mpin_id1, hash_mpin_id1_val = make_octet(None, hash_mpin_id) u1, u1_val = make_octet(None, u) v1, v1_val = make_octet(None, v) y1, y1_val = make_octet(None, y) z1, z1_val = make_octet(None, z) t1, w1_val = make_octet(None, t) hm1, hm1_val = make_octet(PFS) libamcl_core.HASH_ALL(hash_type, hash_mpin_id1, u1, ut1, v1, y1, z1, t1, hm1) hm = to_str(hm1) # clear memory if ut: libamcl_core.OCT_clear(ut1) libamcl_core.OCT_clear(hash_mpin_id1) libamcl_core.OCT_clear(u1) libamcl_core.OCT_clear(v1) libamcl_core.OCT_clear(y1) libamcl_core.OCT_clear(z1) libamcl_core.OCT_clear(t1) return hm def client_key(hash_type, pc1, pc2, pin, r, x, hm, t): """Calculate Key on Client side for M-Pin Full Calculate Key on Client side for M-Pin Full Args:: pc1: precomputed input pc2: precomputed input pin: PIN number r: locally generated random number x: locally generated random number hm: hash of the protocol transcript t: Server-side Diffie-Hellman component Returns:: error_code: error code from the C function client_aes_key: client AES key Raises: """ pc11, pc11_val = make_octet(None, pc1) pc21, pc21_val = make_octet(None, pc2) r1, r1_val = make_octet(None, r) x1, x1_val = make_octet(None, x) hm1, hm1_val = make_octet(None, hm) t1, t1_val = make_octet(None, t) client_aes_key1, client_aes_key_val1 = make_octet(AESKEY_ZZZ) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_CLIENT_KEY( hash_type, pc11, pc21, pin, r1, x1, hm1, t1, client_aes_key1) client_aes_key = to_str(client_aes_key1) # clear memory libamcl_core.OCT_clear(pc11) libamcl_core.OCT_clear(pc21) libamcl_core.OCT_clear(r1) libamcl_core.OCT_clear(x1) libamcl_core.OCT_clear(hm1) libamcl_core.OCT_clear(t1) libamcl_core.OCT_clear(client_aes_key1) return error_code, client_aes_key def server_key(hash_type, z, server_secret, w, hm, HID, u, ut): """Calculate Key on Server side for M-Pin Full Calculate Key on Server side for M-Pin Full.Uses UT internally for the key calculation or uses U if UT is set to None Args:: z: Client-side Diffie-Hellman component server_secret: server secret w: random number generated by the server hm: hash of the protocol transcript HID: H(mpin_id). H is a map to a point on the curve u: u = x.H(ID) ut: ut = x.(H(ID)+H(epoch_date|H(ID))) Returns:: error_code: error code from the C function server_aes_key: server AES key Raises: """ if ut is None: ut1 = ffi.NULL else: ut1, ut1_val = make_octet(None, ut) z1, z1_val = make_octet(None, z) server_secret1, server_secret1_val = make_octet(None, server_secret) w1, w1_val = make_octet(None, w) hm1, hm1_val = make_octet(None, hm) HID1, HID1_val = make_octet(None, HID) u1, u1_val = make_octet(None, u) server_aes_key1, server_aes_key1_val = make_octet(AESKEY_ZZZ) error_code = libamcl_mpin_ZZZ.MPIN_ZZZ_SERVER_KEY( hash_type, z1, server_secret1, w1, hm1, HID1, u1, ut1, server_aes_key1) server_aes_key = to_str(server_aes_key1) # clear memory if ut: libamcl_core.OCT_clear(ut1) libamcl_core.OCT_clear(z1) libamcl_core.OCT_clear(server_secret1) libamcl_core.OCT_clear(w1) libamcl_core.OCT_clear(hm1) libamcl_core.OCT_clear(HID1) libamcl_core.OCT_clear(u1) libamcl_core.OCT_clear(server_aes_key1) return error_code, server_aes_key def aes_gcm_encrypt(aes_key, iv, header, plaintext): """AES-GCM Encryption AES-GCM Encryption Args:: aes_key: AES Key iv: Initialization vector header: header plaintext: Plaintext to be encrypted Returns:: ciphertext: resultant ciphertext tag: MAC Raises: """ aes_key1, aes_key1_val = make_octet(None, aes_key) iv1, iv1_val = make_octet(None, iv) header1, header1_val = make_octet(None, header) plaintext1, plaintext1_val = make_octet(None, plaintext) tag1, tag1_val = make_octet(AESKEY_ZZZ) ciphertext1, ciphertext1_val = make_octet(len(plaintext)) libamcl_core.AES_GCM_ENCRYPT( aes_key1, iv1, header1, plaintext1, ciphertext1, tag1) tag = to_str(tag1) ciphertext = to_str(ciphertext1) # clear memory libamcl_core.OCT_clear(aes_key1) libamcl_core.OCT_clear(iv1) libamcl_core.OCT_clear(header1) libamcl_core.OCT_clear(plaintext1) libamcl_core.OCT_clear(tag1) libamcl_core.OCT_clear(ciphertext1) return ciphertext, tag def aes_gcm_decrypt(aes_key, iv, header, ciphertext): """AES-GCM Decryption AES-GCM Deryption Args:: aes_key: AES Key iv: Initialization vector header: header ciphertext: ciphertext Returns:: plaintext: resultant plaintext tag: MAC Raises: """ aes_key1, aes_key1_val = make_octet(None, aes_key) iv1, iv1_val = make_octet(None, iv) header1, header1_val = make_octet(None, header) ciphertext1, ciphertext1_val = make_octet(None, ciphertext) tag1, tag1_val = make_octet(AESKEY_ZZZ) plaintext1, plaintext1_val = make_octet(len(ciphertext)) libamcl_core.AES_GCM_DECRYPT( aes_key1, iv1, header1, ciphertext1, plaintext1, tag1) tag = to_str(tag1) plaintext = to_str(plaintext1) # clear memory libamcl_core.OCT_clear(aes_key1) libamcl_core.OCT_clear(iv1) libamcl_core.OCT_clear(header1) libamcl_core.OCT_clear(plaintext1) libamcl_core.OCT_clear(tag1) libamcl_core.OCT_clear(ciphertext1) return plaintext, tag def generate_otp(rng): """Generate a random six digit one time password Generate a random six digit one time password Args:: rng: Pointer to cryptographically secure pseudo-random number generator instance Returns:: OTP: One time password Raises: """ OTP = libamcl_mpin_ZZZ.generateOTP(rng) return OTP def generate_random(rng, length): """Generate a random string Generate a random string Args:: rng: Pointer to cryptographically secure pseudo-random number generator instance length: Gives length of random byte array Returns:: random_value: Random value Raises: """ random_value1, random_value1_val = make_octet(length) libamcl_mpin_ZZZ.generateRandom(rng, random_value1) random_value = to_str(random_value1) # clear memory libamcl_core.OCT_clear(random_value1) return random_value if __name__ == "__main__": # Print hex values DEBUG = True # Require user input INPUT = True ONE_PASS = False TIME_PERMITS = True MPIN_ZZZ_FULL = True PIN_ERROR = True if TIME_PERMITS: date = today() else: date = 0 # Seed seed_hex = "78d0fb6705ce77dee47d03eb5b9c5d30" seed = bytes.fromhex(seed_hex) # random number generator rng = create_csprng(seed) # Identity if INPUT: mpin_id = input("Please enter identity:") else: mpin_id = "user@milagro.com" # Hash mpin_id hash_mpin_id = hash_id(HASH_TYPE_ZZZ, mpin_id.encode("utf-8")) if DEBUG: print("mpin_id: {}".format(mpin_id.encode("utf-8").hex())) print("hash_mpin_id: {}".format(hash_mpin_id.hex())) # Generate master secret for MILAGRO and Customer rtn, ms1 = random_generate(rng) if rtn != 0: print("random_generate(rng) Error {}".format(rtn)) rtn, ms2 = random_generate(rng) if rtn != 0: print("random_generate(rng) Error {}".format(rtn)) if DEBUG: print("ms1: {}".format(ms1.hex())) print("ms2: {}".format(ms2.hex())) # Generate server secret shares rtn, ss1 = get_server_secret(ms1) if rtn != 0: print("get_server_secret(ms1) Error {}".format(rtn)) rtn, ss2 = get_server_secret(ms2) if rtn != 0: print("get_server_secret(ms2) Error {}".format(rtn)) if DEBUG: print("ss1: {}".format(ss1.hex())) print("ss2: {}".format(ss2.hex())) # Combine server secret shares rtn, server_secret = recombine_G2(ss1, ss2) if rtn != 0: print("recombine_G2(ss1, ss2) Error {}".format(rtn)) if DEBUG: print("server_secret: {}".format(server_secret.hex())) # Generate client secret shares rtn, cs1 = get_client_secret(ms1, hash_mpin_id) if rtn != 0: print("get_client_secret(ms1, hash_mpin_id) Error {}".format(rtn)) rtn, cs2 = get_client_secret(ms2, hash_mpin_id) if rtn != 0: print("get_client_secret(ms2, hash_mpin_id) Error {}".format(rtn)) if DEBUG: print("cs1: {}".format(cs1.hex())) print("cs2: {}".format(cs2.hex())) # Combine client secret shares rtn, client_secret = recombine_G1(cs1, cs2) if rtn != 0: print("recombine_G1(cs1, cs2) Error {}".format(rtn)) print("Client Secret: {}".format(client_secret.hex())) if TIME_PERMITS: # Generate Time Permit shares if DEBUG: print("Date {}".format(date)) rtn, tp1 = get_client_permit( HASH_TYPE_ZZZ, date, ms1, hash_mpin_id) if rtn != 0: print( "get_client_permit(HASH_TYPE_ZZZ, date, ms1, hash_mpin_id) Error {}".format(rtn)) rtn, tp2 = get_client_permit( HASH_TYPE_ZZZ, date, ms2, hash_mpin_id) if rtn != 0: print( "get_client_permit(HASH_TYPE_ZZZ, date, ms2, hash_mpin_id) Error {}".format(rtn)) if DEBUG: print("tp1: {}".format(tp1.hex())) print("tp2: {}".format(tp2.hex())) # Combine Time Permit shares rtn, time_permit = recombine_G1(tp1, tp2) if rtn != 0: print("recombine_G1(tp1, tp2) Error {}".format(rtn)) if DEBUG: print("time_permit: {}".format(time_permit.hex())) else: time_permit = None # Client extracts PIN from secret to create Token if INPUT: PIN = int(input("Please enter four digit PIN to create M-Pin Token:")) else: PIN = 1234 rtn, token = extract_pin( HASH_TYPE_ZZZ, mpin_id.encode("utf-8"), PIN, client_secret) if rtn != 0: print("extract_pin(HASH_TYPE_ZZZ, mpin_id, PIN, token) Error {}".format(rtn)) print("Token: {}".format(token.hex())) if ONE_PASS: print("M-Pin One Pass") if INPUT: PIN = int(input("Please enter PIN to authenticate:")) else: PIN = 1234 epoch_time = get_time() if DEBUG: print("epoch_time {}".format(epoch_time)) # Client precomputation if MPIN_ZZZ_FULL: rtn, pc1, pc2 = precompute(token, hash_mpin_id) # Client MPIN rtn, x, u, ut, v, y = client(HASH_TYPE_ZZZ, date, mpin_id.encode( "utf-8"), rng, None, PIN, token, time_permit, None, epoch_time) if DEBUG: print("y1 {}".format(y.hex())) if rtn != 0: print("MPIN_ZZZ_CLIENT ERROR {}".format(rtn)) # Client sends Z=r.ID to Server if MPIN_ZZZ_FULL: rtn, r, Z = get_G1_multiple(rng, 1, None, hash_mpin_id) # Server MPIN rtn, HID, HTID, E, F, y2 = server( HASH_TYPE_ZZZ, date, server_secret, u, ut, v, mpin_id.encode("utf-8"), None, epoch_time, None) if DEBUG: print("y2 {}".format(y2.hex())) if rtn != 0: print("ERROR: {} is not authenticated".format(mpin_id)) if PIN_ERROR: err = kangaroo(E, F) print("Client PIN error {} ".format(err)) raise SystemExit(0) else: print("SUCCESS: {} is authenticated".format(mpin_id)) if date: prHID = HTID else: prHID = HID ut = None # Server sends T=w.ID to client if MPIN_ZZZ_FULL: rtn, w, T = get_G1_multiple(rng, 0, None, prHID) if rtn != 0: print("ERROR: Generating T {}".format(rtn)) if MPIN_ZZZ_FULL: HM = hash_all(HASH_TYPE_ZZZ, hash_mpin_id, u, ut, v, y, Z, T) rtn, client_aes_key = client_key( HASH_TYPE_ZZZ, pc1, pc2, PIN, r, x, HM, T) if rtn != 0: print("ERROR: Generating client_aes_key {}".format(rtn)) print("Client AES Key: {}".format(client_aes_key.hex())) rtn, server_aes_key = server_key( HASH_TYPE_ZZZ, Z, server_secret, w, HM, HID, u, ut) if rtn != 0: print("ERROR: Generating server_aes_key {}".format(rtn)) print("Server AES Key: {}".format(server_aes_key.hex())) else: print("M-Pin Three Pass") if INPUT: PIN = int(input("Please enter PIN to authenticate:")) else: PIN = 1234 if MPIN_ZZZ_FULL: rtn, pc1, pc2 = precompute(token, hash_mpin_id) if rtn != 0: print("precompute(token, hash_mpin_id) ERROR {}".format(rtn)) # Client first pass rtn, x, u, ut, sec = client_1(HASH_TYPE_ZZZ, date, mpin_id.encode( "utf-8"), rng, None, PIN, token, time_permit) if rtn != 0: print("client_1 ERROR {}".format(rtn)) if DEBUG: print("x: {}".format(x.hex())) # Server calculates H(ID) and H(T|H(ID)) (if time permits enabled), # and maps them to points on the curve HID and HTID resp. HID, HTID = server_1(HASH_TYPE_ZZZ, date, mpin_id.encode("utf-8")) # Server generates Random number y and sends it to Client rtn, y = random_generate(rng) if rtn != 0: print("random_generate(rng) Error {}".format(rtn)) # Client second pass rtn, v = client_2(x, y, sec) if rtn != 0: print("client_2(x, y, sec) Error {}".format(rtn)) # Server second pass rtn, E, F = server_2(date, HID, HTID, y, server_secret, u, ut, v, None) if rtn != 0: print("ERROR: {} is not authenticated".format(mpin_id)) if PIN_ERROR: err = kangaroo(E, F) print("Client PIN error {}".format(err)) raise SystemExit(0) else: print("SUCCESS: {} is authenticated".format(mpin_id)) # Client sends Z=r.ID to Server if MPIN_ZZZ_FULL: rtn, r, Z = get_G1_multiple(rng, 1, None, hash_mpin_id) if rtn != 0: print("ERROR: Generating Z {}".format(rtn)) if date: prHID = HTID else: prHID = HID ut = None # Server sends T=w.ID to client if MPIN_ZZZ_FULL: rtn, w, T = get_G1_multiple(rng, 0, None, prHID) if rtn != 0: print("ERROR: Generating T {}".format(rtn)) HM = hash_all(HASH_TYPE_ZZZ, hash_mpin_id, u, ut, v, y, Z, T) rtn, client_aes_key = client_key( HASH_TYPE_ZZZ, pc1, pc2, PIN, r, x, HM, T) if rtn != 0: print("ERROR: Generating client_aes_key {}".format(rtn)) print("Client AES Key: {}".format(client_aes_key.hex())) rtn, server_aes_key = server_key( HASH_TYPE_ZZZ, Z, server_secret, w, HM, HID, u, ut) if rtn != 0: print("ERROR: Generating server_aes_key {}".format(rtn)) print("Server AES Key: {}".format(server_aes_key.hex())) if MPIN_ZZZ_FULL: plaintext = "A test message" print("message to encrypt: {}".format(plaintext)) header_hex = "1554a69ecbf04e507eb6985a234613246206c85f8af73e61ab6e2382a26f457d" header = bytes.fromhex(header_hex) iv_hex = "2b213af6b0edf6972bf996fb" iv = bytes.fromhex(iv_hex) ciphertext, tag = aes_gcm_encrypt( client_aes_key, iv, header, plaintext.encode("utf-8")) print("ciphertext {}".format(ciphertext.hex())) print("tag1 {}".format(tag.hex())) plaintext2, tag2 = aes_gcm_decrypt( server_aes_key, iv, header, ciphertext) print("decrypted message: {}".format(plaintext2.decode("utf-8"))) print("tag2 {}".format(tag2.hex())) # Clear memory del seed del hash_mpin_id del mpin_id del ms1 del ms2 del ss1 del ss2 del server_secret del cs1 del cs2 del client_secret del token del x del u del ut del v del y del HID del HTID if PIN_ERROR: del E del F if ONE_PASS: del y2 else: del sec if TIME_PERMITS: del tp1 del tp2 del time_permit if MPIN_ZZZ_FULL: del r del Z del w del T del HM del client_aes_key del server_aes_key del pc1 del pc2 kill_csprng(rng)