MYCSS

2012-12-21

Ініціалізація модуля OTP у токені "Aladdin eToken NG-OTP" за допомогою SDK

Історія

Почалася ця історія з того що компанія придбала токен "Aladdin eToken NG-OTP" для аналізу можливості його впровадження як додаткового засобу захисту інформації.  Був час експериментування, і потім задачі відклалися. З часом я забув пароль до токену, і одним з методів відновлення його роботи була його ініціалізація (Initialize Token) засобами PKI, наприклад "SafeNet Authentication Client". Доступ до токену я отримав, але генерація паролів OTP перстала працювати, і почала показувати помилку на дисплеї - "Err 14".

Теорія

"Aladdin eToken NG-OTP" використовує для генерації одноразових паролів алгоритм HOTP котрий описаний специфікацією IETF RFC 4226.
"Aladdin eToken NG-OTP" у відмінності від "Aladdin eToken PASS", поставляється без файлу з секретним ключем ініціалізації. Хоча є бонус - секретний ключ може бути змінним.

Який ключ є зараз у токені не відомо, про це можемо прочитати у документації
eToken Software Developer's Guide (SDK) 4-86 :
Функція SDK: "SAPI_OTP_GetAttributeValue" - повертає характеристики OTP об'єкту що існує. Значення ключа дуже важливі не не будуть повернуті.
Але секретний ключ ініціалізації можна згенерувати і записати до токену, і тоді він може бути відомий нам.
Функція SDK: "SAPI_OTP_Create" - створює об'єкт OTP.

Вирішення проблеми

Для ініціалізації  існує програмний комплекс SafeNet Authentication Manager (SAM) - система призначена для впровадження, управління, використання та обліку апаратних засобів аутентифікації користувачів в масштабах підприємства. Продається окремо.

Але для моїх потреб на етапі вивчення, цей комплекс не потрібен. Після консультації зі службою підтримки компанії "Aladdin", з'ясував що це можна зробити за допомогою SDK.
І проблема є у тому що для роботи токену  потрібен об'єкт з конфігуриційними параментами OTP, котрі були видаленні у процесі ініціалізації токену.
Тому я зробив запит на отримання SDK і отримав його. На основі наданих у SDK прикладів, створив свою програму для ініціалізації об'єкту OTP у токені - "initOTP.exe" (Win 32, x86), додатково 7-zip архів (eToken OTP by lexxai.7z).
Програма працює у консольному режимі, тому треба запускати її з cmd.exe.
Використовувати програму "InitOTP" треба так: 

InitOTP <user-password> [[init counter] [hex secure key string]]
 Examples: 
  Random key usage: InitOTP 1234567890 
  Init count passw: InitOTP 1234567890 55 
  Fixed key usage : InitOTP 1234567890 0 b09cdbe8475ecca8ca22d31c798b96b6e2d831f10f0d581f

Де, "user-password" - пароль доступу до eToken,  по замовчуванні він є "1234567890".
"init counter" - початковий номер паролю OTP (0 - по замовчуванню),
"hex secure key string" - бажаний ключ, якщо його не вказувати буде його згенеровано автоматично.

Результатом роботи програми буде створено новий об'єкт OTP, з заданим ключем або ключем згенерованим автоматично, з визначеним початковим порядковим номером паролю OTP.
Надалі програма запросить натиснути клавішу генерації одноразового паролю (на токені) і таким чином перевірить якість згенерованого пароля.
По закінченню перевірки програма видасть до консолі ключ у шістнадцятирічному форматі довжиною 24 байти (192 біти), котрий треба зберегти і використовувати у програмах на стороні сервера, наприклад у файлі - "users.otp" для "mod_authn_otp". Після чого токен готовий до використання і його можна витягнути з роз'єму USB.

Приклади роботи

Запуск без параметрів, показує приклади використання
Генерація випадкового ключа, нумерація паролів з №0
Генерація випадкового ключа, нумерація паролів з №2208
Генерація заданим ключем, нумерація паролів з №3994

Застосування 

Таким чином знаючи секретний ключ ініціалізації можна використовувати Aladdin eToken NG-OTP у своїх реалізаціях, використовуючи, наприклад як PHP модуль , або у RADIUS сервері, або модуль сервера Apache - mod_authn_otp, чи для Модулів аутентифікації - PAM.

  • Застосування з використанням PHP

Використати дописану мною програму на PHP, на основі цієї публікації. Програма запитує у користувача два послідовно отриманих від токену паролі, знаючи ключ токену (прописаний у тілі програми), методом перебору визначає який поточний номер паролю. Ця програм також може використовується для автентифікації введеного одноразового паролю.
Використання:
php etoken.php pass=894206
result 894206 is count :16


php etoken.php pass=294306 pass1=357914
Sync password ok. Result - next user count will be :24
  • Застосування з використанням mod_authn_otp

Одноразові паролі (OTP) та сервер Apache (mod_authn_otp)

Додаток

З початковим код програми на С++ можете ознайомитися завантажив її : initOTP.сpp

 initOTP.cpp:
/////////////////////////////////////////////////////////////////////////////
// eToken SDK Sample
// 
//  LICENSE AGREEMENT:
//  1. COPYRIGHTS AND TRADEMARKS
//  The eTokenTM system and its documentation are copyright (C) 1985 to present,
//  by Aladdin Knowledge Systems Ltd. All rights reserved.
//
//  eToken is a trademark and ALADDIN KNOWLEDGE SYSTEMS LTD is a registered trademark 
//  of Aladdin Knowledge Systems Ltd. All  other  trademarks,  brands,  and product 
//  names used in this guide are trademarks of their respective owners.
//
//  2. Title & Ownership
//  THIS IS A LICENSE AGREEMENT AND NOT AN AGREEMENT FOR SALE. 
//  The Code IS NOT FOR SALE and is and shall remain as Aladdin's sole property. 
//  All right, title and interest in and to the Code, including associated 
//  intellectual property rights, in and to the Code are and will remain with Aladdin.
//
//  3.   Disclaimer of Warranty
//  THE CODE CONSTITUTES A CODE SAMPLE AND IS NOT A COMPLETE PRODUCT AND MAY CONTAIN 
//  DEFECTS, AND PRODUCE UNINTENDED OR ERRONEOUS RESULTS. THE CODE IS PROVIDED "AS IS", 
//  WITHOUT WARRANTY OF ANY KIND. ALADDIN DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR 
//  IMPLIED, INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
//  AND FITNESS FOR A PARTICULAR PURPOSE.
//  The entire risk arising out of the use or performance of the Code remains with you.
//
//  4.   No Liability For Damages
//  Without derogating from the above, in no event shall Aladdin be liable for any damages 
//  whatsoever (including, without limitation, damages for loss of business profits, business 
//  interruption, loss of business information, or other pecuniary loss) arising out of the 
//  use of or inability to use the Code, even if Aladdin has been advised of the possibility 
//  of such damages. Your sole recourse in the event of any dissatisfaction with the Code is 
//  to stop using it and return it.
/////////////////////////////////////////////////////////////////////////////
//
//   adopted by lexxai, http://lexxai.blogspot.com
//
/////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32#include <windows.h>
#else
#include "wintypes.h"
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>
#endif
#include "eTPkcs11.h"
#include "eTSAPI.h"
#ifdef _WIN32#define PKCS11_DLL_NAME         "etpkcs11.dll"
#define ETSAPI_DLL_NAME         "etsapi.dll"
#else
#define PKCS11_DLL_NAME         "libeTPkcs11.so"
#define ETSAPI_DLL_NAME         "libeTSapi.so"
#define LoadLibrary(lib)        dlopen(lib,RTLD_NOW)
#define GetProcAddress          dlsym#define FreeLibrary(lib)        dlclose(lib)
typedef void * HINSTANCE;
#endif//_WIN32  
// Print message and stop execution.
static void leave(const char * message)
{
        if (message) printf("%s\n", message);
        printf("Press the Enter key to exit");
        getchar();
        exit(message ? -1 : 0);
}

CK_FUNCTION_LIST_PTR fl = NULL; // PKCS#11 functions list.
// Load eTPKCS11.dll and initialize PKCS#11.
void LoadPKCS11()
{
        HINSTANCE hLib = LoadLibrary(PKCS11_DLL_NAME); 
        if (!hLib) leave("Cannot load PKCS11");

        CK_C_GetFunctionList f_C_GetFunctionList = NULL;
#ifdef _WIN32
        (FARPROC&)f_C_GetFunctionList= GetProcAddress(hLib, "C_GetFunctionList");
#else
        *(void**)&f_C_GetFunctionList= GetProcAddress(hLib, "C_GetFunctionList");
#endif  
        if (!f_C_GetFunctionList) leave("C_GetFunctionList not found");

        if (CKR_OK != f_C_GetFunctionList(&fl)) leave("C_GetFunctionList failed");
        if (CKR_OK != fl->C_Initialize(0)) leave("C_Initialize failed");
}
// SAPI functions declaration.
typedef CK_RV (*t_SAPI_OTP_Create)(
        CK_SESSION_HANDLE hSession, 
        CK_ATTRIBUTE_PTR pTemplate, 
        CK_ULONG ulCount);
typedef CK_RV (*t_SAPI_OTP_Destroy)(
        CK_SESSION_HANDLE hSession);
typedef CK_RV (*t_SAPI_OTP_Execute)(
        CK_SESSION_HANDLE hSession, 
        CK_ULONG mode, 
        CK_CHAR_PTR pResult, 
        CK_ULONG_PTR pSize);
typedef CK_RV (*t_SAPI_OTP_GetMechanismInfo)(
        CK_SLOT_ID slotId, 
        CK_ULONG mechanism, 
        CK_SAPI_OTP_MECHANISM_INFO_PTR pMechanismInfo);
typedef CK_RV (*t_SAPI_Server_OTP_Calculate)(
        CK_ATTRIBUTE_PTR pTemplate, 
        CK_ULONG ulCount, 
        CK_CHAR_PTR pResult, 
        CK_ULONG_PTR pSize);

t_SAPI_OTP_Create _SAPI_OTP_Create = NULL;
t_SAPI_OTP_Destroy _SAPI_OTP_Destroy = NULL;
t_SAPI_OTP_Execute _SAPI_OTP_Execute = NULL;
t_SAPI_OTP_GetMechanismInfo _SAPI_OTP_GetMechanismInfo = NULL;
t_SAPI_Server_OTP_Calculate _SAPI_Server_OTP_Calculate = NULL;
// Load eTSAPI.dll and acquire its usable functions.
void LoadETSAPI()
{
        HINSTANCE hLib = LoadLibrary(ETSAPI_DLL_NAME); 
        if (!hLib) leave("Cannot load etsapi");
#ifdef _WIN32
        (FARPROC&)_SAPI_OTP_Create = GetProcAddress(hLib, "SAPI_OTP_Create");
#else
        *(void**)&_SAPI_OTP_Create = GetProcAddress(hLib, "SAPI_OTP_Create");
#endif
        if (!_SAPI_OTP_Create) leave("SAPI_OTP_Create not found");
#ifdef _WIN32
        (FARPROC&)_SAPI_OTP_Destroy = GetProcAddress(hLib, "SAPI_OTP_Destroy");
#else
        *(void**)&_SAPI_OTP_Destroy = GetProcAddress(hLib, "SAPI_OTP_Destroy");
#endif
        if (!_SAPI_OTP_Destroy) leave("SAPI_OTP_Delete not found");
#ifdef _WIN32
        (FARPROC&)_SAPI_OTP_Execute = GetProcAddress(hLib, "SAPI_OTP_Execute");
#else
        *(void**)&_SAPI_OTP_Execute = GetProcAddress(hLib, "SAPI_OTP_Execute");
#endif
        if (!_SAPI_OTP_Execute) leave("SAPI_OTP_Execute not found");
#ifdef _WIN32
        (FARPROC&)_SAPI_OTP_GetMechanismInfo = GetProcAddress(hLib, "SAPI_OTP_GetMechanismInfo");
#else
        *(void**)&_SAPI_OTP_GetMechanismInfo = GetProcAddress(hLib, "SAPI_OTP_GetMechanismInfo");
#endif
        if (!_SAPI_OTP_GetMechanismInfo) leave("SAPI_OTP_GetMechanismInfo not found");
#ifdef _WIN32
        (FARPROC&)_SAPI_Server_OTP_Calculate = GetProcAddress(hLib, "SAPI_Server_OTP_Calculate");
#else
        *(void**)&_SAPI_Server_OTP_Calculate = GetProcAddress(hLib, "SAPI_Server_OTP_Calculate");
#endif
        if (!_SAPI_Server_OTP_Calculate) leave("SAPI_Server_OTP_Calculate not found");
}
// Locate an inserted eToken.
CK_SLOT_ID LocateToken()
{
        CK_ULONG nSlots = 1;
        CK_SLOT_ID nSlotID;
        if (CKR_OK != fl->C_GetSlotList(TRUE, &nSlotID, &nSlots)) leave("C_GetSlotList failed");
        if (nSlots<1) leave("No eToken inserted");
        return nSlotID;
}
int main(int argc, char* argv[])
{
        LoadPKCS11();
        LoadETSAPI();

        CK_SLOT_ID nSlotID = LocateToken();

        // Open PKCS#11 session.
        CK_SESSION_HANDLE hSession;
        CK_RV rv = fl->C_OpenSession(nSlotID, CKF_RW_SESSION|CKF_SERIAL_SESSION, NULL, NULL, &hSession);
        if (rv!=0) leave("C_OpenSession failed");

        if(argc < 2)
        {
                leave("Usage:\n"
                        " InitOTP <user-password> [[init counter] [hex secure key string]]\n"
                        "  Examples:\n   Random key usage: InitOTP 1234567890\n"
                        "   Init count passw: InitOTP 1234567890 55\n"
                        "   Fixed key usage : InitOTP 1234567890 0 b09cdbe8475ecca8ca22d31c798b96b6e2d831f10f0d581f\n"
                        );
        }

        const char* Pass = argv[1];
        CK_ULONG initcounter;
        initcounter=0;
        const char *SecureKey = argv[3], *pos=SecureKey;
        // Perform PKCS#11 login.
        rv = fl->C_Login(hSession, CKU_USER, (CK_CHAR*) Pass, 10);
        if (rv!=0) leave("\nError. user-password is wrong for this token\n");

        printf("Clean OTP object\n");
        _SAPI_OTP_Destroy(hSession); // Clean up.

        printf("Create OTP object\n");
        
        if (argc > 2) 
        {
                initcounter=atoi(argv[2]);
        }
        printf("\nInit counter is %li\n\n",initcounter);

        BYTE key[24];
        memset (key,0,sizeof(key)); // default fill array of key by 0x00
        if (argc > 3)
        {
                //Fixed key value used from user
                //detect wrong length of key string
                if (!(strlen(SecureKey)>0))
                {
                        leave("\nError. Enter correct secure key");
                }
                if (strlen(SecureKey)>48)
                {
                        printf("\nWarning. For secure key will be used only first 24 bytes\n");
                }
                //Convert hex string to byte array
                while( *pos )
                {
                        if( !((pos-SecureKey)&1) )
                                sscanf_s(pos,"%02x",&key[(pos-SecureKey)>>1]);
                        ++pos;
                }
                printf("Used fixed key with length:%d bytes\n\n",sizeof(key));
        }
        else
        {
                // Generate random key value
                //JV Card OTP MinKeySize = 20, MaxKeySize = 24
                //CardOS OTP MinKeySize = 20, MaxKeySize = 32.
                rv = fl->C_GenerateRandom(hSession, key, sizeof(key));
                if (rv!=0) leave("C_GenerateRandom failed");
                printf("Generated radnom key with length:%d bytes\n\n",sizeof(key));
        }

        for (CK_ULONG counter = 0; counter<sizeof(key); counter++) 
        {
                printf("%02x",(const char *) (const char *) key[counter]);
        }
        printf("\n\n");



        // Create new OTP object.
        CK_ULONG mech = CK_SAPI_OTP_HMAC_SHA1_DEC6;
        CK_BBOOL ck_false = FALSE;
        CK_ATTRIBUTE tCreate[] = {
                {CKA_SAPI_OTP_MECHANISM,    &mech,        sizeof(CK_ULONG)}, 
                {CKA_SAPI_OTP_VALUE,        key,          sizeof(key)     },
                {CKA_SAPI_OTP_NEXT_ALLOWED, &ck_false,     sizeof(CK_BBOOL)},
                {CKA_SAPI_OTP_COUNTER,      &initcounter,     sizeof(CK_ULONG)},
        };
        rv = _SAPI_OTP_Create(hSession, tCreate, sizeof(tCreate)/sizeof(CK_ATTRIBUTE));
        if (rv) leave("SAPI_OTP_Create failed");

        // Check if eToken supports the OTP button in connected mode.
        CK_SAPI_OTP_MECHANISM_INFO mi;
        rv = _SAPI_OTP_GetMechanismInfo(nSlotID, CK_SAPI_OTP_HMAC_SHA1_DEC6, &mi);
        if (rv) leave("SAPI_OTP_GetMechanismInfo failed");
        if ((mi.flags & CK_SAPI_OTP_BUTTON_SUPPORTED)==0)
        {
                leave("This token does not support the OTP button in connected mode");
        }

        // Get current OTP value.
        printf("Acquire current OTP\n");
        CK_CHAR OTP_current[7];
        CK_ULONG OTPLen_current = sizeof(OTP_current);
        rv = _SAPI_OTP_Execute(hSession, CK_SAPI_OTP_CURRENT, OTP_current, &OTPLen_current);
        if (rv) leave("SAPI_OTP_Execute failed");

        printf("Acquire next OTP. Press eToken button to generate next OTP value.\n");
        CK_CHAR OTP[7];
        CK_ULONG OTPLen = sizeof(OTP);
        BOOL bChanged = FALSE;
        while (!bChanged)
        {
                // Get current OTP value.
#ifdef _WIN32
                Sleep(100);
#else
                usleep(100);
#endif
                rv = _SAPI_OTP_Execute(hSession, CK_SAPI_OTP_CURRENT, OTP, &OTPLen);
                if (rv) leave("SAPI_OTP_Execute failed");
                bChanged = (OTPLen_current!=OTPLen) || (memcmp(OTP_current, OTP, OTPLen)); 
        }

        rv = _SAPI_OTP_Execute(hSession, CK_SAPI_OTP_RELEASE, OTP, &OTPLen);

        printf("Calculate OTP on the server side\n");
        BOOL bSuccess = FALSE;
        CK_ULONG coun;
        CK_ULONG window;
        window=5000;

        for (CK_ULONG counter = 0; !bSuccess && counter<window; counter++) // Maximum of 5000 tries on the server side.
        {
                CK_CHAR Result[7];
                CK_ULONG ResultLen = sizeof(Result);

                // OTP object template.
                CK_ATTRIBUTE tExecute[] = {
                        {CKA_SAPI_OTP_MECHANISM,    &mech,        sizeof(CK_ULONG)}, 
                        {CKA_SAPI_OTP_VALUE,        key,          sizeof(key)     },
                        {CKA_SAPI_OTP_COUNTER,      &counter,     sizeof(CK_ULONG)},
                };

                // Server side calculation.
                rv = _SAPI_Server_OTP_Calculate(tExecute, sizeof(tExecute)/sizeof(CK_ATTRIBUTE), Result, &ResultLen);
                if (rv) leave("SAPI_Server_OTP_Calculate failed\n");

                bSuccess = (ResultLen==OTPLen) && (!memcmp(Result, OTP, OTPLen)); // Check result.
                coun=counter;
        }

        if (!bSuccess) printf("Client/Server OTP mismatch, or Init counter more than %i\n",window);
        else printf("OTP was successfully verified on the server side, key number: %i \n\n", coun);

        //f_SAPI_OTP_Destroy(hSession);
        fl->C_Finalize(0);
        leave(NULL);
        return 0;
}
 (c) 2012. lexxai

P.S.  На сьогодні, 2019 рік, використовуючи Windows 10, та SafeNetAuthenticationClient-x32-x64-10.4, програма не працює за відсутністю бібліотеки.
#define ETSAPI_DLL_NAME         "etsapi.dll"

3 коментарі:

Андрій сказав...

Добрий день.
На роботі мені видали ключ з сертифікатом, і я зіткнувся з такою проблемою, як і Ви (при натисканні на кнопку генерації пароля видає помилку 14, тому й віддали мені, що заблокували його). В мене виникло бажання відновити його роботу, і в пошуках рішення я знайшов Ваш блог.
Біда в тому, що Ваша програма InitOTP недоступна по посиланню.
Чи не могли б Ви знову її викласти? Буду дуже вдячний.
Дякую.

lexxai сказав...

eToken OTP by lexxai.7z
Додатково додав https://drive.google.com/open?id=0ByJHIt7DSY3cejFmQzRHQ1IxSDQ

Андрій сказав...

Я декілька разів хотів Вам подякувати, та все щось збивало. Тому, нехай і через рік, дуже Вам дякую!!!

Коли забув ти рідну мову, біднієш духом ти щодня...
When you forgot your native language you would become a poor at spirit every day ...

Д.Білоус / D.Bilous
Рабів до раю не пускають. Будь вільним!

ipv6 ready