blob: 7cacb65f97f741a952e3f879b4cc29064eed74b7 [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.
*/
/*
MIRACL JavaScript M-Pin Authentication Functions
Provides these functions:
calculateMPinToken Calculates the MPin Token
getLocalEntropy Gets an entropy value from the client machine
initializeRNG Initialize the Random Number Generator
addShares Add two points on the curve that are originally in hex format
pass1Request Form the JSON request for pass one of the M-Pin protocol
pass2Request Form the JSON request for pass two of the M-Pin protocol
passRequest Form the JSON request for one pass M-Pin protocol
*/
/*
Run LINT tool;
jslint MPINAuth.js
expected output;
MPINAuth.js
#1 Read only.
MPINAuth = {}; // Line 61, Pos 1
#2 Unexpected '('.
if (typeof (window) === 'undefined') { // Line 134, Pos 16
#3 Unexpected 'typeof'. Use '===' to compare directly with undefined.
if (typeof (window) === 'undefined') { // Line 134, Pos 9
#4 Unexpected '('.
if (typeof (crypto) !== 'undefined') { // Line 139, Pos 16
#5 Unexpected 'typeof'. Use '===' to compare directly with undefined.
if (typeof (crypto) !== 'undefined') { // Line 139, Pos 9
*/
/*global MPIN */
/*global MPINAuth */
/*global RAND */
/*global Uint32Array */
/*jslint browser: true*/
/*jslint plusplus: true */
MPINAuth = {};
// Random Number Generator
MPINAuth.rng = new RAND();
// Pass 1 values
MPINAuth.SEC = [];
MPINAuth.X = [];
// Default value for debug output
MPINAuth.DEBUG = false;
// Errors
MPINAuth.BAD_HEX = -20;
MPINAuth.BAD_BYTES = -21;
/* Calculates the MPin Token
This function convert mpin_id _hex to unicode. It then maps the mpin_id
to a point on the curve, multiplies this value by PIN and then subtracts
it from the client_secret curve point to generate the M-Pin token.
Args:
PIN: Four digit PIN
client_secret_hex: Hex encoded client secret
mpin_id_hex: Hex encoded M-Pin ID
Returns:
mpin_token_hex: Hex encoded M-Pin Token
*/
MPINAuth.calculateMPinToken = function (mpin_id_hex, PIN, client_secret_hex) {
"use strict";
var client_secret_bytes, mpin_id_bytes, token_hex, error_code;
client_secret_bytes = [];
mpin_id_bytes = [];
if (MPINAuth.DEBUG) {console.log("MPINAuth.calculateMPinToken client_secret_hex: " + client_secret_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.calculateMPinToken mpin_id_hex: " + mpin_id_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.calculateMPinToken PIN: " + PIN); }
client_secret_bytes = MPINAuth.hextobytes(client_secret_hex);
mpin_id_bytes = MPINAuth.hextobytes(mpin_id_hex);
error_code = MPIN.EXTRACT_PIN(mpin_id_bytes, PIN, client_secret_bytes);
if (error_code !== 0) {
console.log("MPINAuth.calculateMPinToken error_code: " + error_code);
return error_code;
}
token_hex = MPIN.bytestostring(client_secret_bytes);
if (MPINAuth.DEBUG) {console.log("MPINAuth.calculateMPinToken token_hex: " + token_hex); }
return token_hex;
};
/* Get local entropy
This function makes a call to /dev/urandom for a 256 bit value
Args:
NA
Returns:
entropy_val: 256 bit random value or null
*/
MPINAuth.getLocalEntropy = function () {
"use strict";
var crypto, array, entropy_val, i, hex_val;
if (typeof (window) === 'undefined') {
if (MPINAuth.DEBUG) {console.log("MPINAuth.getLocalEntropy Test mode without browser"); }
return "";
}
crypto = (window.crypto || window.msCrypto);
if (typeof (crypto) !== 'undefined') {
array = new Uint32Array(8);
crypto.getRandomValues(array);
entropy_val = "";
for (i = 0; i < array.length; i++) {
hex_val = array[i].toString(16);
entropy_val = entropy_val + hex_val;
}
if (MPINAuth.DEBUG) {console.log("MPINAuth.getLocalEntropy len(entropy_val): " + entropy_val.length + " entropy_val: " + entropy_val); }
return entropy_val;
}
return "";
};
/* Initialize the Random Number Generator (RNG)
This function uses an external and, where available, a
local entropy source to initialize a RNG.
Args:
seed_value: External seed value for RNGTurn on generation of local entropy
Returns:
*/
MPINAuth.initializeRNG = function (seed_hex) {
"use strict";
var local_entropy_hex, entropy_hex, entropy_bytes;
local_entropy_hex = MPINAuth.getLocalEntropy();
entropy_hex = local_entropy_hex + seed_hex;
if (MPINAuth.DEBUG) {console.log("MPINAuth.initializeRNG seed_val_hex: " + seed_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.initializeRNG local_entropy_hex: " + local_entropy_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.initializeRNG entropy_hex: " + entropy_hex); }
entropy_bytes = MPINAuth.hextobytes(entropy_hex);
MPINAuth.rng.clean();
MPINAuth.rng.seed(entropy_bytes.length, entropy_bytes);
};
/* Add two points on the curve that are originally in hex format
This function is used to add client secret or time permits shares.
Args:
share1_hex: Hex encoded point on the curve which represents
a time permit or client secret share
share2_hex: Hex encoded point on the curve which represents
a time permit or client secret share
Returns:
sum_hex: Hex encoded sum of the shares
*/
MPINAuth.addShares = function (share1_hex, share2_hex) {
"use strict";
var share1_bytes, share2_bytes, sum_bytes, error_code, sum_hex;
share1_bytes = [];
share2_bytes = [];
sum_bytes = [];
if (MPINAuth.DEBUG) {console.log("MPINAuth.addShares share1_hex: " + share1_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.addShares share2_hex: " + share2_hex); }
share1_bytes = MPINAuth.hextobytes(share1_hex);
share2_bytes = MPINAuth.hextobytes(share2_hex);
error_code = MPIN.RECOMBINE_G1(share1_bytes, share2_bytes, sum_bytes);
if (error_code !== 0) {
console.log("MPINAuth.addShares error_code: " + error_code);
return error_code;
}
sum_hex = MPIN.bytestostring(sum_bytes);
if (MPINAuth.DEBUG) {console.log("MPINAuth.addShares sum_hex: " + sum_hex); }
return sum_hex;
};
/* Form the JSON request for pass one of the M-Pin protocol
This function assigns to the property X a random value. It assigns to
the property SEC the sum of the client secret and time permit. It also
calculates the values U and UT which are required for M-Pin authentication,
where U = X.(map_to_curve(MPIN_ID)) and UT = X.(map_to_curve(MPIN_ID) + map_to_curve(DATE|sha256(MPIN_ID))
UT is called the commitment. U is the required for finding the PIN error.
Args:
mpin_id_hex: Hex encoded M-Pin ID
token_hex: Hex encoded M-Pin Token
timePermit_hex: Hex encoded Time Permit
PIN: PIN for authentication
epoch_days: The number of epoch days.
X_hex: X value generated externally. This is used for test.
Returns:
{
mpin_id: mpin_id_hex,
UT: UT_hex,
U: U_hex,
pass: 1
}
where;
mpin_id: Hex encoded M-Pin ID
UT: Hex encoded X.(map_to_curve(MPIN_ID) + map_to_curve(DATE|sha256(MPIN_ID))
U: Hex encoded X.(map_to_curve(MPIN_ID))
pass: Protocol first pass
*/
MPINAuth.pass1Request = function (mpin_id_hex, token_hex, timePermit_hex, PIN, epoch_days, X_hex) {
"use strict";
var UT_hex, U_hex, date, error_code, mpin_id_bytes, token_bytes, timePermit_bytes, U, UT, request;
mpin_id_bytes = [];
token_bytes = [];
timePermit_bytes = [];
U = [];
UT = [];
request = {};
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request mpin_id_hex: " + mpin_id_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request token_hex: " + token_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request timePermit_hex: " + timePermit_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request PIN: " + PIN); }
if (MPINAuth.DEBUG) {console.log("mpinAuth.pass1Request epoch_days: " + epoch_days); }
// The following is used for test
if (X_hex !== null) {
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request X: " + X_hex); }
MPINAuth.X = MPINAuth.hextobytes(X_hex);
MPINAuth.rng = null;
}
mpin_id_bytes = MPINAuth.hextobytes(mpin_id_hex);
token_bytes = MPINAuth.hextobytes(token_hex);
timePermit_bytes = MPINAuth.hextobytes(timePermit_hex);
error_code = MPIN.CLIENT_1(epoch_days, mpin_id_bytes, MPINAuth.rng, MPINAuth.X, PIN, token_bytes, MPINAuth.SEC, U, UT, timePermit_bytes);
if (error_code !== 0) {
console.log("MPINAuth.pass1Request error_code: " + error_code);
return error_code;
}
UT_hex = MPIN.bytestostring(UT);
U_hex = MPIN.bytestostring(U);
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request MPINAuth.rng: " + MPINAuth.rng); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request MPINAuth.X: " + MPIN.bytestostring(MPINAuth.X)); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request MPINAuth.SEC: " + MPIN.bytestostring(MPINAuth.SEC)); }
// Form request
request = {
mpin_id: mpin_id_hex,
UT: UT_hex,
U: U_hex,
pass: 1
};
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass1Request request: "); }
if (MPINAuth.DEBUG) {console.dir(request); }
return request;
};
/* Form the JSON request for pass two of the M-Pin protocol
This function uses the random value y from the server, property X
and the combined client secret and time permit to calculate
the value V which is sent to the M-Pin server.
Args:
y_hex: Random value supplied by server
Returns:
{
V: V_hex,
OTP: requestOTP,
WID: accessNumber,
pass: 2
}
where;
V: Value required by the server to authenticate user
OTP: Request OTP: 1 = required
WID: Number required for mobile authentication
pass: Protocol second pass
*/
MPINAuth.pass2Request = function (y_hex, requestOTP, accessNumber) {
"use strict";
var y_bytes, x_hex, SEC_hex, error_code, V_hex, request;
request = {};
y_bytes = MPINAuth.hextobytes(y_hex);
x_hex = MPIN.bytestostring(MPINAuth.X);
SEC_hex = MPIN.bytestostring(MPINAuth.SEC);
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass2Request x_hex: " + x_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass2Request y_hex: " + y_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass2Request SEC_hex: " + SEC_hex); }
// Compute V
error_code = MPIN.CLIENT_2(MPINAuth.X, y_bytes, MPINAuth.SEC);
if (error_code !== 0) {
console.log("MPINAuth.pass2Request error_code: " + error_code);
return error_code;
}
V_hex = MPIN.bytestostring(MPINAuth.SEC);
// Form reuest
request = {
V: V_hex,
OTP: requestOTP,
WID: accessNumber,
pass: 2
};
if (MPINAuth.DEBUG) {console.log("MPINAuth.pass2Request request: "); }
if (MPINAuth.DEBUG) {console.dir(request); }
return request;
};
/* Convert a hex representation of a Point to bytes
This function converts a hex value to a bytes array
Args:
hex_value: Hex encoded byte value
Returns:
byte_value: Input value in bytes
*/
MPINAuth.hextobytes = function (value_hex) {
"use strict";
var len, byte_value, i;
len = value_hex.length;
byte_value = [];
for (i = 0; i < len; i += 2) {
byte_value[(i / 2)] = parseInt(value_hex.substr(i, 2), 16);
}
return byte_value;
};
/* Form the JSON request for single pass M-Pin protocol
This function performs the client side M-Pin protocol
It also calculates the values U and UT which are required for M-Pin authentication,
where U = X.(map_to_curve(MPIN_ID)) and UT = X.(map_to_curve(MPIN_ID) + map_to_curve(DATE|sha256(MPIN_ID))
UT is called the commitment. U is the required for finding the PIN error.
Args:
mpin_id_hex: Hex encoded M-Pin ID
token_hex: Hex encoded M-Pin Token
timePermit_hex: Hex encoded Time Permit
PIN: PIN for authentication
requestOTP: Reqeuest a One Time Password
accessNumber: Access number for desktop authentication
timeValue: Epoch time
Returns:
{
mpin_id: mpin_id_hex,
U: U_hex,
UT: UT_hex,
V: V_hex,
T: timeValue,
OTP: requestOTP,
WID: accessNumber
}
where;
mpin_id: Hex encoded M-Pin ID
U: Hex encoded X.(map_to_curve(MPIN_ID))
UT: Hex encoded X.(map_to_curve(MPIN_ID) + map_to_curve(DATE|sha256(MPIN_ID))
V: Value required by the server to authenticate user
T: Epoch time
OTP: Request OTP: 1 = required
WID: Number required for mobile authentication
*/
MPINAuth.passRequest = function (mpin_id_hex, token_hex, timePermit_hex, PIN, requestOTP, accessNumber, epoch_days, timeValue, X_hex) {
"use strict";
var X, Y, SEC, UT_hex, U_hex, date, error_code, mpin_id_bytes, token_bytes, timePermit_bytes, U, UT, V_hex, request;
X = [];
Y = [];
SEC = [];
mpin_id_bytes = [];
token_bytes = [];
timePermit_bytes = [];
U = [];
UT = [];
request = {};
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest mpin_id_hex: " + mpin_id_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest token_hex: " + token_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest timePermit_hex: " + timePermit_hex); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest PIN: " + PIN); }
if (MPINAuth.DEBUG) {console.log("mpinAuth.passRequest timeValue: " + timeValue); }
mpin_id_bytes = MPINAuth.hextobytes(mpin_id_hex);
token_bytes = MPINAuth.hextobytes(token_hex);
if (timePermit_hex === null) {
date = 0;
} else {
timePermit_bytes = MPINAuth.hextobytes(timePermit_hex);
date = epoch_days;
}
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest date: " + date); }
// The following is used for test
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest X: " + X_hex); }
if (X_hex !== null) {
X = MPINAuth.hextobytes(X_hex);
MPINAuth.rng = null;
}
error_code = MPIN.CLIENT(date, mpin_id_bytes, MPINAuth.rng, X, PIN, token_bytes, SEC, U, UT, timePermit_bytes, timeValue, Y);
if (error_code !== 0) {
console.log("MPINAuth.passRequest error_code: " + error_code);
return error_code;
}
UT_hex = MPIN.bytestostring(UT);
U_hex = MPIN.bytestostring(U);
V_hex = MPIN.bytestostring(SEC);
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest MPINAuth.rng: " + MPINAuth.rng); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest X: " + MPIN.bytestostring(X)); }
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest Y: " + MPIN.bytestostring(Y)); }
// Form request
request = {
mpin_id: mpin_id_hex,
U: U_hex,
UT: UT_hex,
V: V_hex,
T: timeValue,
OTP: requestOTP,
WID: accessNumber
};
if (MPINAuth.DEBUG) {console.log("MPINAuth.passRequest request: "); }
if (MPINAuth.DEBUG) {console.dir(request); }
return request;
};