blob: ebc991cac3df51f6e5da3f3dd83011c1508c8245 [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.
*/
/*
* Internal M-Pin Crypto interface Non-TEE implementation
*/
#include "mpin_crypto_non_tee.h"
typedef MPinSDK::String String;
typedef MPinSDK::Status Status;
class Octet : public octet
{
public:
Octet(size_t maxSize);
Octet(const String& str);
~Octet();
String ToString();
public:
static const int TOKEN_SIZE = 2 * PFS + 1;
static const int GROUP_SIZE = PGS;
};
Octet::Octet(size_t maxSize)
{
this->max = 0;
this->len = 0;
this->val = (char *) calloc(maxSize, 1);
if(this->val != NULL)
{
this->max = maxSize;
}
}
Octet::Octet(const String& str)
{
this->max = 0;
this->len = 0;
size_t maxSize = str.size();
this->val = (char *) malloc(maxSize);
if(this->val != NULL)
{
this->max = maxSize;
this->len = maxSize;
memcpy(this->val, str.c_str(), maxSize);
}
}
Octet::~Octet()
{
if(this->val != NULL)
{
memset(this->val, 0, this->max);
free(this->val);
}
}
String Octet::ToString()
{
return String(this->val, this->len);
}
MPinCryptoNonTee::MPinCryptoNonTee() : m_storage(NULL), m_initialized(false), m_sessionOpened(false)
{
}
MPinCryptoNonTee::~MPinCryptoNonTee()
{
Destroy();
}
Status MPinCryptoNonTee::Init(IStorage *storage)
{
if(m_initialized)
{
return Status(Status::OK);
}
m_storage = storage;
String tokensJson;
if(!storage->GetData(tokensJson))
{
return Status(Status::STORAGE_ERROR, String().Format("Failed to load data from storage: '%s'", storage->GetErrorMessage().c_str()));
}
m_tokens.Clear();
tokensJson.Trim();
if(tokensJson.length() > 0)
{
if(!m_tokens.Parse(tokensJson.c_str()))
{
return Status(Status::STORAGE_ERROR, String("Failed to parse tokens json"));
}
}
m_initialized = true;
return Status(Status::OK);
}
void MPinCryptoNonTee::Destroy()
{
if(m_initialized)
{
CloseSession();
m_initialized = false;
}
}
Status MPinCryptoNonTee::OpenSession()
{
if(m_sessionOpened)
{
return Status(Status::CRYPTO_ERROR, String("Session is already opened"));
}
if(!m_initialized)
{
return Status(Status::CRYPTO_ERROR, String("Not initialized"));
}
m_sessionOpened = true;
return Status(Status::OK);
}
void MPinCryptoNonTee::CloseSession()
{
if(m_sessionOpened)
{
ForgetPass2Data();
m_sessionOpened = false;
}
}
Status MPinCryptoNonTee::Register(UserPtr user, const String& pin, std::vector<String>& clientSecretShares)
{
const String& mpinId = user->GetMPinId();
if(!m_sessionOpened)
{
return Status(Status::CRYPTO_ERROR, String("Session is not opened or not initialized"));
}
// Combine the secret shares
if(clientSecretShares.size() != 2)
{
return Status(Status::CRYPTO_ERROR, String().Format("Expecting 2 client secret shares (provided=%d)", (int) clientSecretShares.size()));
}
Octet cs1(clientSecretShares[0]);
Octet cs2(clientSecretShares[1]);
Octet token(Octet::TOKEN_SIZE);
int res = MPIN_RECOMBINE_G1(&cs1, &cs2, &token);
if(res)
{
return Status(Status::CRYPTO_ERROR, String().Format("MPIN_RECOMBINE_G1() failed with code %d", res));
}
if(pin.empty())
{
return Status(Status::PIN_INPUT_CANCELED, "Pin input canceled");
}
// Extract the pin from the secret
Octet cid(mpinId);
res = MPIN_EXTRACT_PIN(&cid, pin.GetHash(), &token);
if(res)
{
return Status(Status::CRYPTO_ERROR, String().Format("MPIN_EXTRACT_PIN() failed with code %d", res));
}
// Save the token securely
if(!StoreToken(mpinId, token.ToString()))
{
return Status(Status::STORAGE_ERROR, String("Failed to store token"));
}
return Status(Status::OK);
}
Status MPinCryptoNonTee::AuthenticatePass1(UserPtr user, const String& pin, int date, std::vector<String>& timePermitShares, String& commitmentU, String& commitmentUT)
{
const String& mpinId = user->GetMPinId();
if(!m_sessionOpened)
{
return Status(Status::CRYPTO_ERROR, String("Session is not opened or not initialized"));
}
Octet timePermit(Octet::TOKEN_SIZE);
// Valid date (date != 0) means authentication *with* time permit and 2 time permit shares are expected
// Invalid date (date == 0) means authentication with *no* time permit and time permit shares are ignored
if(date != 0)
{
// Combine time permit shares
if(timePermitShares.size() != 2)
{
return Status(Status::CRYPTO_ERROR, String().Format("Expecting 2 time permit shares (provided=%d)", (int) timePermitShares.size()));
}
Octet tp1(timePermitShares[0]);
Octet tp2(timePermitShares[1]);
int res = MPIN_RECOMBINE_G1(&tp1, &tp2, &timePermit);
if(res)
{
return Status(Status::CRYPTO_ERROR, String().Format("MPIN_RECOMBINE_G1() failed with code %d", res));
}
}
// Get the stored token
String tokenStr = GetToken(mpinId);
if(tokenStr.length() == 0)
{
return Status(Status::CRYPTO_ERROR, String().Format("Failed to find stored token for mpinId='%s'", mpinId.c_str()));
}
if(pin.empty())
{
return Status(Status::PIN_INPUT_CANCELED, "Pin input canceled");
}
// Gather the parameters for Authentication pass 1
Octet cid(mpinId);
Octet seed(100);
seed.len = seed.max;
GenerateRandomSeed(seed.val, seed.len);
csprng rng;
CREATE_CSPRNG(&rng,&seed);
Octet x(Octet::GROUP_SIZE);
Octet token(tokenStr);
Octet clientSecret(Octet::TOKEN_SIZE);
Octet u(Octet::TOKEN_SIZE);
Octet ut(Octet::TOKEN_SIZE);
// Authentication pass 1
int res = MPIN_CLIENT_1(date, &cid, &rng, &x, pin.GetHash(), &token, &clientSecret, &u, &ut, &timePermit);
if(res)
{
return Status(Status::CRYPTO_ERROR, String().Format("MPIN_CLIENT_1() failed with code %d", res));
}
KILL_CSPRNG(&rng);
commitmentU = u.ToString();
commitmentUT = ut.ToString();
SaveDataForPass2(mpinId, clientSecret.ToString(), x.ToString());
return Status(Status::OK);
}
Status MPinCryptoNonTee::AuthenticatePass2(UserPtr user, const String& challenge, String& validator)
{
const String& mpinId = user->GetMPinId();
if(!m_sessionOpened)
{
return Status(Status::CRYPTO_ERROR, String("Session is not opened or not initialized"));
}
if(mpinId != m_mpinId)
{
return Status(Status::CRYPTO_ERROR, String("Wrong mpinId passed for authentication pass 2"));
}
Octet x(m_x);
Octet y(challenge);
Octet v(m_clientSecret);
int res = MPIN_CLIENT_2(&x, &y, &v);
ForgetPass2Data();
if(res)
{
return Status(Status::CRYPTO_ERROR, String().Format("MPIN_CLIENT_2() failed with code %d", res));
}
validator = v.ToString();
return Status(Status::OK);
}
Status MPinCryptoNonTee::SaveRegOTT(const String& mpinId, const String& regOTT)
{
if(!m_initialized)
{
return Status(Status::CRYPTO_ERROR, String("Not initialized"));
}
try
{
String mpinIdHex = util::HexEncode(mpinId);
json::Object::iterator i = m_tokens.Find(mpinIdHex);
if (i == m_tokens.End())
{
json::Object item;
item["regOTT"] = json::String(regOTT);
m_tokens[mpinIdHex] = item;
}
else
{
i->element["regOTT"] = json::String(regOTT);
}
m_storage->SetData(m_tokens.ToString());
return Status(Status::OK);
}
catch(const json::Exception& e)
{
return Status(Status::STORAGE_ERROR, e.what());
}
}
Status MPinCryptoNonTee::LoadRegOTT(const String& mpinId, OUT String& regOTT)
{
if(!m_initialized)
{
return Status(Status::CRYPTO_ERROR, String("Not initialized"));
}
try
{
String mpinIdHex = util::HexEncode(mpinId);
json::Object::iterator i = m_tokens.Find(mpinIdHex);
if (i == m_tokens.End())
{
regOTT.clear();
return Status(Status::OK);
}
regOTT = json::String( i->element["regOTT"] ).Value();
return Status(Status::OK);
}
catch(const json::Exception& e)
{
return Status(Status::STORAGE_ERROR, e.what());
}
}
Status MPinCryptoNonTee::DeleteRegOTT(const String& mpinId)
{
if(!m_initialized)
{
return Status(Status::CRYPTO_ERROR, String("Not initialized"));
}
try
{
String mpinIdHex = util::HexEncode(mpinId);
json::Object::iterator i = m_tokens.Find(mpinIdHex);
if (i == m_tokens.End())
{
return Status(Status::OK);
}
else
{
json::Object& item = (json::Object&) i->element;
i = item.Find("regOTT");
if (i == item.End())
{
return Status(Status::OK);
}
else
{
std::string& regOTT = ((json::String&) i->element).Value();
util::OverwriteString(regOTT);
item.Erase(i);
}
}
if(!m_storage->SetData(m_tokens.ToString()))
{
return Status(Status::STORAGE_ERROR, m_storage->GetErrorMessage());
}
return Status(Status::OK);
}
catch(const json::Exception& e)
{
return Status(Status::STORAGE_ERROR, e.what());
}
}
bool MPinCryptoNonTee::StoreToken(const String& mpinId, const String& token)
{
if(token.length() == 0)
{
return false;
}
String mpinIdHex = util::HexEncode(mpinId);
try
{
json::Object element;
element["token"] = json::String(util::HexEncode(token));
m_tokens[mpinIdHex] = element;
}
catch(json::Exception e)
{
return false;
}
String tokensJson = m_tokens.ToString();
if(tokensJson.length() == 0)
{
return false;
}
if(!m_storage->SetData(tokensJson))
{
return false;
}
return true;
}
void MPinCryptoNonTee::DeleteToken(const String& mpinId)
{
try
{
json::Object::iterator i = m_tokens.Find(util::HexEncode(mpinId));
if(i == m_tokens.End())
{
return;
}
m_tokens.Erase(i);
m_storage->SetData(m_tokens.ToString());
}
catch(json::Exception)
{
}
}
String MPinCryptoNonTee::GetToken(const String& mpinId)
{
try
{
json::Object::iterator i = m_tokens.Find(util::HexEncode(mpinId));
if(i == m_tokens.End())
{
return "";
}
return util::HexDecode((std::string) ((json::String) i->element["token"]));
}
catch(json::Exception e)
{
return "";
}
}
void MPinCryptoNonTee::SaveDataForPass2(const String& mpinId, const String& clientSecret, const String& x)
{
ForgetPass2Data();
m_mpinId = mpinId;
m_clientSecret = clientSecret;
m_x = x;
}
void MPinCryptoNonTee::ForgetPass2Data()
{
m_mpinId.Overwrite();
m_clientSecret.Overwrite();
m_x.Overwrite();
}
void MPinCryptoNonTee::GenerateRandomSeed(char *buf, size_t len)
{
// TODO: This should be changed/improved to generate some real entropy
// TODO: seedValue from client settings must be included here
#ifndef _WIN32
FILE *fp = fopen("/dev/urandom", "rb");
if(fp != NULL)
{
size_t rc = fread(buf, 1, len, fp);
fclose(fp);
}
#else
srand((unsigned int) time(NULL));
for(size_t i = 0; i < len; ++i)
{
int r = (rand() * 256) / RAND_MAX;
buf[i] = r;
}
#endif
}