//--------------------------------------------------------------------------- // Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES // OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name of Dallas Semiconductor // shall not be used except as stated in the Dallas Semiconductor // Branding Policy. //--------------------------------------------------------------------------- // // shadebit.c - Transaction-level functions for SHA Debits without a // hardware coprocessor. Also, this file contains some // Protocol-level functions to replace their 'hardware-only' // counterparts.. // // Version: 2.10 #include "shaibutton.h" #define INITIAL_BALANCE 100000 #define MAX_RETRY_CNT 255 //------------------------------------------------------------------------- // Installs new service data on a user token. // // 'copr' - Structure for holding coprocessor information. // 'user' - Structure for holding user token information. // 'secret' - the authentication secret to install on user token. // // Return: If TRUE, new service installation succeeded. // If FALSE, an error occurred. // SMALLINT InstallServiceData(SHACopr* copr, SHAUser* user, uchar* secret, int secret_length) { short handle; int maxwrite; FileEntry fe; uchar fullBindCode[15]; //make sure user has a file directory structure memcpy(fe.Name, copr->serviceFilename, 4); fe.Ext = copr->serviceFilename[4]; // install master authentication secret if(user->devAN[0]==0x18) { //need to format the device if(!owFormat(user->portnum, user->devAN)) return FALSE; //and create an empty stub for his account information if(!owCreateFile(user->portnum, user->devAN, &maxwrite, &handle, &fe)) return FALSE; //need to know what page the stub is on user->accountPageNumber = fe.Spage; // set the serial number to that of the user owSerialNum(user->portnum, user->devAN, FALSE); OWASSERT( InstallSystemSecret(user->portnum, user->accountPageNumber, user->accountPageNumber&7, secret, secret_length, FALSE), OWERROR_INSTALL_SECRET_FAILED, FALSE ); } else if(user->devAN[0]==0x33) { // set the serial number to that of the user owSerialNum(user->portnum, user->devAN, FALSE); //because of copy-authorization, we need to install the //secret first on the DS1961S and _then_ format the system OWASSERT( InstallSystemSecret33(user->portnum, 0, 0, secret, secret_length, FALSE), OWERROR_INSTALL_SECRET_FAILED, FALSE ); //need to format the device if(!owFormat(user->portnum, user->devAN)) return FALSE; //and create an empty stub for his account information if(!owCreateFile(user->portnum, user->devAN, &maxwrite, &handle, &fe)) return FALSE; //need to know what page the stub is on user->accountPageNumber = fe.Spage; } else { return FALSE; } // format the bind code properly // first four bytes of bind code memcpy(fullBindCode, copr->bindCode, 4); // followed by the pagenumber fullBindCode[4] = (uchar)user->accountPageNumber; // and 7 bytes of the address of current device memcpy(&fullBindCode[5], user->devAN, 7); // followed by the last 3 bytes of bind code memcpy(&fullBindCode[12], &(copr->bindCode[4]), 3); // create a unique secret for iButton if(user->devAN[0]==0x18) { OWASSERT( BindSecretToiButton(user->portnum, user->accountPageNumber, user->accountPageNumber&7, copr->bindData, fullBindCode, TRUE), OWERROR_BIND_SECRET_FAILED, FALSE ); // do a read just to get value of writecycle counter user->writeCycleCounter = ReadAuthPageSHA(user->portnum, user->accountPageNumber, user->accountFile.raw, NULL, TRUE); } else if(user->devAN[0]==0x33) { OWASSERT( BindSecretToiButton33(user->portnum, user->accountPageNumber, user->accountPageNumber&7, copr->bindData, fullBindCode, TRUE), OWERROR_BIND_SECRET_FAILED, FALSE ); // Call VerifyUser just to get the user's secret in wspc if(!VerifyUser(copr, user, TRUE)) return FALSE; } //setup user account file with initial balance IntToBytes(user->accountFile.file.balanceBytes,3,INITIAL_BALANCE); // set transaction ID user->accountFile.file.transID[0] = 0; user->accountFile.file.transID[0] = 0; //sign the data with coprocessor and write it out return UpdateServiceData(copr, user); } //------------------------------------------------------------------------- // Updates service data on a user token. This includes signing the // data if the part is a DS1963S. // // 'copr' - Structure for holding coprocessor information. // 'user' - Structure for holding user token information. // // Return: If TRUE, update succeeded. // If FALSE, an error occurred. // SMALLINT UpdateServiceData(SHACopr* copr, SHAUser* user) { ushort crc16, i; uchar scratchpad[32]; // make sure length is right. user->accountFile.file.fileLength = 29; // update transaction ID // doesn't matter what it is, just needs to change user->accountFile.file.transID[0] += 1; if(user->accountFile.file.transID[0]==0) user->accountFile.file.transID[1] += 1; // conversion factor - 2 data bytes user->accountFile.file.convFactor[0] = (uchar)0x8B; user->accountFile.file.convFactor[1] = (uchar)0x48; // clear out the old signature and CRC memcpy(user->accountFile.file.signature, copr->initSignature, 20); memset(user->accountFile.file.crc16, 0x00, 2); // reset data type code user->accountFile.file.dataTypeCode = 0; if(user->devAN[0]!=0x33) { // --- Set up the scratchpad for signing memset(scratchpad, 0x00, 32); // the write cycle counter +1 (since we are about to write this to it) if(user->writeCycleCounter>0) IntToBytes(&scratchpad[8], 4, user->writeCycleCounter+1); else // user doesn't have write cycle counter (DS1961S) memset(&scratchpad[8], 0x0FF, 4); // the pagenumber scratchpad[12] = (uchar)user->accountPageNumber; // and 7 bytes of the address of current device memcpy(&scratchpad[13], user->devAN, 7); // the coprocessor's signing challenge memcpy(&scratchpad[20], copr->signChlg, 3); OWASSERT( CreateDataSignature(copr, user->accountFile.raw, scratchpad, user->accountFile.file.signature, TRUE), OWERROR_SIGN_SERVICE_DATA_FAILED, FALSE ); } //add the crc at the end of the data. setcrc16(user->portnum, user->accountPageNumber); for (i = 0; i < 30; i++) crc16 = docrc16(user->portnum,user->accountFile.raw[i]); crc16 = ~crc16; user->accountFile.file.crc16[0] = (uchar)crc16; user->accountFile.file.crc16[1] = (uchar)(crc16>>8); // set the serial number to that of the user owSerialNum(user->portnum, user->devAN, FALSE); if(user->devAN[0]==0x18) { //DS1963S - not too tough OWASSERT( WriteDataPageSHA(user->portnum, user->accountPageNumber, user->accountFile.raw, FALSE), OWERROR_WRITE_DATA_PAGE_FAILED, FALSE ); } else if(user->devAN[0]==0x33) { //DS1961S - a bit tougher //assumes sign_secret for this coprocessor already has //the user's unique secret installed uchar pageContents[32], data[32]; uchar MAC[20]; int addr = user->accountPageNumber << 5; OWASSERT( ReadMemoryPageSHA33(user->portnum, user->accountPageNumber, pageContents, FALSE), OWERROR_READ_MEMORY_PAGE_FAILED, FALSE ); //PrintHexLabeled("Page contents", pageContents, 32); //PrintHexLabeled("Replace with", user->accountFile.raw, 32); for(i=0; i<32; i+=8) { if(memcmp(&pageContents[i],&user->accountFile.raw[i],8) != 0) { //PrintHexLabeled("Current Page contents", pageContents, 32); //PrintHexLabeled(" 8 bytes to replace", &pageContents[i], 8); //PrintHexLabeled(" with these 8 bytes", &user->accountFile.raw[i], 8); memcpy(data, pageContents, 28); memcpy(&data[28], &user->accountFile.raw[i], 4); memcpy(&scratchpad[8], &user->accountFile.raw[i+4], 4); scratchpad[12] = (uchar)(user->accountPageNumber&0x3F); memcpy(&scratchpad[13], user->devAN, 7); memset(&scratchpad[20], 0xFF, 3); // this function changes the serial number to that of copr OWASSERT( CreateDataSignature(copr, data, scratchpad, MAC, TRUE), OWERROR_SIGN_SERVICE_DATA_FAILED, FALSE); // set the serial number back to that of the user owSerialNum(user->portnum, user->devAN, FALSE); OWASSERT( WriteScratchpadSHA33(user->portnum, addr+i, &user->accountFile.raw[i], FALSE), OWERROR_WRITE_SCRATCHPAD_FAILED, FALSE ); OWASSERT( CopyScratchpadSHA33(user->portnum, addr+i, MAC, TRUE), OWERROR_COPY_SCRATCHPAD_FAILED, FALSE ); memcpy(&pageContents[i], &user->accountFile.raw[i], 8); } } } else return FALSE; return TRUE; } //------------------------------------------------------------------------- // Verifies a user token as a valid member of the system. First, a random // challenge is generated. Then, the user must answer that challenge. // The user's response signature is then verified against the proper // response.. // // 'copr' - Structure for holding coprocessor information. // 'user' - Structure for holding user token information. // 'doBind' - if true, the user's unique secret is recreated on the // coprocessor. If this function is called multiple times, // it is acceptable to skip the bind for all calls after // the first on the same user token. // // Return: If TRUE, user was verified. // If FALSE, an error occurred or user verification failed. // SMALLINT VerifyUser(SHACopr* copr, SHAUser* user, SMALLINT doBind) { uchar chlg[3]; OWASSERT( CreateChallenge(copr, 1, chlg, 0), OWERROR_CREATE_CHALLENGE_FAILED, FALSE ); OWASSERT( AnswerChallenge(user, chlg)>=0, OWERROR_ANSWER_CHALLENGE_FAILED, FALSE ); OWASSERT( VerifyAuthResponse(copr, user, chlg, doBind), OWERROR_VERIFY_AUTH_RESPONSE_FAILED, FALSE ); return TRUE; } //------------------------------------------------------------------------- // Verifies service data on a user token as a valid member of the system. // Pre-condition: must call verify user first. // // 'copr' - Structure for holding coprocessor information. // 'user' - Structure for holding user token information. // // Return: If TRUE, data was verified. // If FALSE, an error occurred or data verification failed. // SMALLINT VerifyData(SHACopr* copr, SHAUser* user) { uchar scratchpad[32]; uchar acctFile[32]; if(user->devAN[0]==0x33) { //DS1961S carry unsigned transaction data, //no need to verify return TRUE; } memset(scratchpad, 0x00, 32); if(user->writeCycleCounter>0) IntToBytes(&scratchpad[8], 4, user->writeCycleCounter); else // user doesn't have write cycle counter (DS1961S) memset(&scratchpad[8], 0x0FF, 4); // the pagenumber scratchpad[12] = (uchar)user->accountPageNumber; // and 7 bytes of the address of current device memcpy(&scratchpad[13], user->devAN, 7); // the coprocessor's signing challenge memcpy(&scratchpad[20], copr->signChlg, 3); // make a copy of the account file memcpy(acctFile, user->accountFile.raw, 32); // clear out the old signature and CRC memcpy(&acctFile[8], copr->initSignature, 20); memset(&acctFile[30], 0x00, 2); OWASSERT( CreateDataSignature(copr, acctFile, scratchpad, user->accountFile.file.signature, FALSE), OWERROR_SIGN_SERVICE_DATA_FAILED, FALSE ); return TRUE; } //------------------------------------------------------------------------- // Performs debit of service data on a user token, re-signs the data, and // writes it back to the user token. // Pre-condition: must call verify user first. // // 'copr' - Structure for holding coprocessor information. // 'user' - Structure for holding user token information. // 'debitAmount' - the amount of money to debit from balance in cents. // 'verifySuccess' - Paranoid double-check of account write by re-calling // VerifyUser(copr,user) // // Return: If TRUE, data was updated. // If FALSE, an error occurred or couldn't verify success. // SMALLINT ExecuteTransaction(SHACopr* copr, SHAUser* user, int debitAmount, SMALLINT verifySuccess ) { SMALLINT success, dataOK = TRUE; uchar oldAcctData[32], newAcctData[32]; int cnt = MAX_RETRY_CNT; int balance, oldBalance ; memcpy(oldAcctData, user->accountFile.raw, 32); oldBalance = BytesToInt(user->accountFile.file.balanceBytes, 3); if(oldBalance<=0) { OWERROR(OWERROR_BAD_SERVICE_DATA); return FALSE; } balance = oldBalance - debitAmount; IntToBytes(user->accountFile.file.balanceBytes, 3, balance); success = UpdateServiceData(copr, user); //if write didn't succeeded or if we need to perform //a verification step anyways, let's double-check what //the user has on the button. if(verifySuccess || !success) { dataOK = FALSE; //save what the account data is supposed to look like memcpy(newAcctData, user->accountFile.raw, 32); do { //calling verify user re-issues a challenge-response //and reloads the cached account data in the user object. //Does not re-bind the user's unique secret if(VerifyUser(copr,user,FALSE)) { //compare the user's account data against the working //copy and the backup copy. if( memcmp(user->accountFile.raw, newAcctData, 32) == 0 ) { //looks like it worked dataOK = TRUE; } else if( memcmp(user->accountFile.raw, oldAcctData, 32) == 0 ) { //if it matches the backup copy, we didn't write anything //and the data is still okay, but we didn't do a debit dataOK = TRUE; success = FALSE; OWERROR(OWERROR_SERVICE_DATA_NOT_UPDATED); } else { // retry the write success = UpdateServiceData(copr, user); //save what the account data is supposed to look like memcpy(newAcctData, user->accountFile.raw, 32); } } } while(dataOK==FALSE && cnt-->0); } if(!dataOK) { //couldn't fix the data after 255 retries OWERROR(OWERROR_CATASTROPHIC_SERVICE_FAILURE); success = FALSE; } return success; }