/// <summary> /// <P>Setup account data on a fresh user iButton. Prior to calling /// setup transaction data, the authentication secret for the iButton /// should already be setup and a directory entry (as well as at least /// an empty placeholder file) should exist for the account data. If /// you constructed the SHAiButtonUser using /// <code>SHAiButtonUser(SHAiButtonCopr,OneWireContainer18,bool,byte[])</code> /// the secret has been setup for you and you should know call this /// function. If you try to install the authentication secret after /// creating the account data, you will destroy all account data on the /// iButton.</P> /// /// <P>You can set the value of the intial account balance by calling /// <code>transaction.setParameter(SHADebit.INITIAL_AMOUNT,10000)</code> /// where the value of the units is in cents (i.e. 10000 = $100).</P> /// /// <P><B>Flow of action: </B> /// <ul> /// <li> Generate generic account page </li> /// <li> Insert the initial balance into account data </li> /// <li> Insert the (constant) digital signature </li> /// <li> Write account data page to iButton </li> /// </ul></P> /// </summary> /// <param name="user"> SHAiButtonUser upon which the transaction occurs. /// </param> /// <returns> <code>true</code>if and only if the signature is /// successfully created by the coprocessor AND the data is /// successfully written to the user iButton. /// </returns> /// <seealso cref= SHAiButtonUser#writeAccountData(byte[],int) </seealso> /// <seealso cref= #getLastError() </seealso> public override bool setupTransactionData(SHAiButtonUser user) { //clear any error lastError = NO_ERROR; // not in critical path, so malloc'ing is okay byte[] accountData = new byte[32]; return(writeTransactionData(user, 0, this.initialAmount, accountData)); }
/// <summary> /// Does the writing of transaction data to the user button as well /// as actually signing the data with the coprocessor. /// /// No need to synchronize wince the methods that call this /// private method will be synchronized. /// </summary> private bool writeTransactionData(SHAiButtonUser user, int transID, int balance, byte[] accountData) { //init local vars int acctPageNum = user.AccountPageNumber; // data type code - dynamic: 0x00, static: 0x01 accountData[I_DATA_TYPE_CODE] = 0x01; // conversion factor - 2 data bytes accountData[I_CONVERSION_FACTOR + 0] = 0x8B; accountData[I_CONVERSION_FACTOR + 1] = 0x48; // zero-out the don't care bytes accountData[I_DONT_CARE] = 0x00; accountData[I_DONT_CARE + 1] = 0x00; accountData[I_DONT_CARE + 2] = 0x00; accountData[I_DONT_CARE + 3] = 0x00; if (accountData[I_FILE_LENGTH] == RECORD_A_LENGTH) { OneWireEventSource.Log.Debug("Was A, now using B"); // length of the TMEX file accountData[I_FILE_LENGTH] = RECORD_B_LENGTH; // account balance - 3 data bytes Convert.toByteArray(balance, accountData, I_BALANCE_B, 3); // transaction ID - 2 data bytes accountData[I_TRANSACTION_ID_B + 0] = (byte)transID; accountData[I_TRANSACTION_ID_B + 1] = (byte)((int)((uint)transID >> 8)); // continuation pointer for TMEX file accountData[I_CONTINUATION_PTR_B] = 0x00; // clear out the crc16 - 2 data bytes accountData[I_FILE_CRC16_B + 0] = 0x00; accountData[I_FILE_CRC16_B + 1] = 0x00; // dump in the inverted CRC int crc = ~CRC16.compute(accountData, 0, accountData[I_FILE_LENGTH] + 1, acctPageNum); accountData[I_FILE_CRC16_B + 0] = (byte)crc; accountData[I_FILE_CRC16_B + 1] = (byte)(crc >> 8); } else { OneWireEventSource.Log.Debug("Was B, now using A"); // length of the TMEX file accountData[I_FILE_LENGTH] = RECORD_A_LENGTH; // account balance - 3 data bytes Convert.toByteArray(balance, accountData, I_BALANCE_A, 3); // transaction ID - 2 data bytes accountData[I_TRANSACTION_ID_A + 0] = (byte)transID; accountData[I_TRANSACTION_ID_A + 1] = (byte)((int)((uint)transID >> 8)); // continuation pointer for TMEX file accountData[I_CONTINUATION_PTR_A] = 0x00; // clear out the crc16 - 2 data bytes accountData[I_FILE_CRC16_A + 0] = 0x00; accountData[I_FILE_CRC16_A + 1] = 0x00; // dump in the inverted CRC int crc = ~CRC16.compute(accountData, 0, accountData[I_FILE_LENGTH] + 1, acctPageNum); accountData[I_FILE_CRC16_A + 0] = (byte)crc; accountData[I_FILE_CRC16_A + 1] = (byte)(crc >> 8); } OneWireEventSource.Log.Debug("------------------------------------"); OneWireEventSource.Log.Debug("writing transaction data"); OneWireEventSource.Log.Debug("acctPageNum: " + acctPageNum); OneWireEventSource.Log.Debug("accountData: " + Convert.toHexString(accountData)); OneWireEventSource.Log.Debug("------------------------------------"); // write it to the button try { if (user.writeAccountData(accountData, 0)) { return(true); } } catch (OneWireException owe) { OneWireEventSource.Log.Debug(owe.ToString()); } this.lastError = SHATransaction.USER_WRITE_DATA_FAILED; return(false); }
/// <summary> /// <P>Performs the unsigned debit, subtracting the debit amount from /// the user's balance and storing the new, unsigned account data on the /// user's iButton. The debit amount can be set using /// <code>transaction.setParameter(SHADebit.DEBIT_AMOUNT, 50)</code>, /// where the value is in units of cents (i.e. for 1 dollar, use 100).</P> /// /// <P><B>Flow of action: </B> /// <ul> /// <li> Read the account data from user </li> /// <li> Extract account balance </li> /// <li> Assert balance greater than debit amount </li> /// <li> Debit money from balance </li> /// <li> Insert the new balance </li> /// <li> Write the account data to the user </li> /// </ul></P> /// /// <P>If previous steps have been executed, all "Read" commands on /// the user are reading from cached data.</P> /// </summary> /// <param name="user"> SHAiButtonUser upon which the transaction occurs. /// </param> /// <returns> <code>true</code> if and only if the user has enough in the /// account balance to perform the requested debit AND the account data /// has been written to the button. /// </returns> /// <seealso cref= SHAiButtonUser#readAccountData(byte[],int) </seealso> /// <seealso cref= SHAiButtonUser#writeAccountData(byte[],int) </seealso> /// <seealso cref= #getLastError() </seealso> public override bool executeTransaction(SHAiButtonUser user, bool verifySuccess) { lock (this) { //clear any error this.lastError = NO_ERROR; //init local vars //holds the working copy of account data byte[] accountData = this.executeTransaction_accountData; //holds the backup copy of account data before writing byte[] oldAcctData = this.executeTransaction_oldAcctData; //holds the account data read back for checking byte[] newAcctData = this.executeTransaction_newAcctData; //just make the transaction ID a random number, so it changes int transID = rand.Next(); //if verifyUser was called, this is a read of cached data user.readAccountData(accountData, 0); //before we update the account data array at all, let's make a backup copy Array.Copy(accountData, 0, oldAcctData, 0, 32); //get the user's balance from accountData int balance = -1; if (accountData[I_FILE_LENGTH] == RECORD_A_LENGTH) { balance = Convert.toInt(accountData, I_BALANCE_A, 3); } else if (accountData[I_FILE_LENGTH] == RECORD_B_LENGTH) { balance = Convert.toInt(accountData, I_BALANCE_B, 3); } //if there are insufficient funds if (this.debitAmount > balance) { this.lastError = USER_BAD_ACCOUNT_DATA; return(false); } //update the user's balance this.userBalance = (balance - this.debitAmount); // attempt to update the page bool success = false; try { success = writeTransactionData(user, transID, this.userBalance, accountData); } catch (System.Exception) { // sink } //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) { OneWireEventSource.Log.Debug("attempting to re-write transaction data: "); OneWireEventSource.Log.Debug("cur Data: " + Convert.toHexString(accountData)); OneWireEventSource.Log.Debug("old data: " + Convert.toHexString(oldAcctData)); bool dataOK = false; int cnt = MAX_RETRY_CNT; do { try { // let's refresh the page user.refreshDevice(); //calling verify user re-issues a challenge-response //and reloads the cached account data in the user object. if (verifyUser(user)) { //compare the user's account data against the working //copy and the backup copy. if (user.readAccountData(newAcctData, 0)) { OneWireEventSource.Log.Debug("new data: " + Convert.toHexString(newAcctData)); bool isOld = true; bool isCur = true; for (int i = 0; i < 32 && (isOld || isCur); i++) { //match the backup isOld = isOld && (newAcctData[i] == oldAcctData[i]); //match the working copy isCur = isCur && (newAcctData[i] == accountData[i]); } if (isOld) { //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; } else if (isCur) { dataOK = true; success = true; } else { int cnt2 = MAX_RETRY_CNT; do { //iBUTTON DATA IS TOTALLY HOSED //keep trying to get account data on the button try { success = writeTransactionData(user, transID, this.userBalance, accountData); } catch (OneWireIOException owioe) { if (cnt2 == 0) { throw owioe; } } catch (OneWireException owe) { if (cnt2 == 0) { throw owe; } } } while (((cnt2 -= 1) > 0) && !success); } } } } catch (OneWireIOException owioe) { if (cnt == 0) { throw owioe; } } catch (OneWireException owe) { if (cnt == 0) { throw owe; } } } while (!dataOK && ((cnt -= 1) > 0)); //TODO if (!dataOK) { //couldn't fix the data after 255 retries IOHelper.writeLine("Catastrophic Failure!"); success = false; } } return(success); } }
/// <summary> /// <P>Verifies user's account data. Account data is "verified" if and /// only if the account balance is greater than zero. No digital /// signature is checked by this transaction.</P> /// /// <P><B>Flow of action: </B> /// <ul> /// <li> Read the account data from user </li> /// <li> Extract account balance </li> /// <li> Debit money from balance </li> /// <li> Insert the new balance </li> /// <li> Write the account data to the user </li> /// </ul></P> /// /// <P>If previous steps have been executed, all "Read" commands on /// the user are reading from cached data.</P> /// </summary> /// <param name="user"> SHAiButtonUser upon which the transaction occurs. /// </param> /// <returns> <code>true</code> if and only if the account balance is /// greater than zero. /// </returns> /// <seealso cref= SHAiButtonUser#readAccountData(byte[],int) </seealso> /// <seealso cref= #getLastError() </seealso> public override bool verifyTransactionData(SHAiButtonUser user) { lock (this) { //clear any error this.lastError = NO_ERROR; byte[] accountData = this.verifyData_accountData; //if verifyUser was called, this is a read of cached data user.readAccountData(accountData, 0); // verify the A-B data scheme is valid bool validPtr = false, validA = false, validB = false; byte fileLength = accountData[I_FILE_LENGTH]; int crc16 = CRC16.compute(accountData, 0, fileLength + 3, user.AccountPageNumber); if (fileLength == RECORD_A_LENGTH || fileLength == RECORD_B_LENGTH) { validPtr = true; } // was the crc of the file correct? if (crc16 == 0xB001) { // if the header points to a valid record, we're done if (validPtr) { // nothing more to check for DS1961S/DS2432, since // it carries no signed data, we're finished return(true); } // header points to neither record A nor B, but // crc is absolutely correct. that can only mean // we're looking at something that is the wrong // size from what was expected, but apparently is // exactly what was meant to be written. I'm done. this.lastError = USER_BAD_ACCOUNT_DATA; return(false); } // restore the other header information accountData[I_DATA_TYPE_CODE] = 0x01; accountData[I_CONVERSION_FACTOR + 0] = 0x8B; accountData[I_CONVERSION_FACTOR + 1] = 0x48; // zero-out the don't care bytes accountData[I_DONT_CARE] = 0x00; accountData[I_DONT_CARE + 1] = 0x00; accountData[I_DONT_CARE + 2] = 0x00; accountData[I_DONT_CARE + 3] = 0x00; // lets try Record A and check the crc accountData[I_FILE_LENGTH] = RECORD_A_LENGTH; crc16 = CRC16.compute(accountData, 0, RECORD_A_LENGTH + 3, user.AccountPageNumber); if (crc16 == 0xB001) { validA = true; } // lets try Record B and check the crc accountData[I_FILE_LENGTH] = RECORD_B_LENGTH; crc16 = CRC16.compute(accountData, 0, RECORD_B_LENGTH + 3, user.AccountPageNumber); if (crc16 == 0xB001) { validB = true; } if (validA && validB) { OneWireEventSource.Log.Debug("Both A and B are valid"); // Both A & B are valid! And we know that we can only // get here if the pointer or the header was not valid. // That means that B was the last updated one but the // header got hosed and the debit was not finished... // which means A is the last known good value, let's go with A. accountData[I_FILE_LENGTH] = RECORD_A_LENGTH; } else if (validA) { OneWireEventSource.Log.Debug("A is valid not B"); // B is invalid, A is valid. Means A is the last updated one, // but B is the last known good value. The header was not updated // to point to A before debit was aborted. Let's go with B accountData[I_FILE_LENGTH] = RECORD_B_LENGTH; } else if (validB) { OneWireEventSource.Log.Debug("B is valid not A - impossible"); // A is invalid, B is valid. Should never ever happen. Something // got completely hosed. What should happen here? this.lastError = USER_BAD_ACCOUNT_DATA; return(false); } else { OneWireEventSource.Log.Debug("Neither record has valid CRC"); // neither record contains a valid CRC. What should happen here? // probably got weak bit in ptr record, no telling which way to go this.lastError = USER_BAD_ACCOUNT_DATA; return(false); } //get the user's balance from accountData int balance = -1; if (accountData[I_FILE_LENGTH] == RECORD_A_LENGTH) { balance = Convert.toInt(accountData, I_BALANCE_A, 3); } else if (accountData[I_FILE_LENGTH] == RECORD_B_LENGTH) { balance = Convert.toInt(accountData, I_BALANCE_B, 3); } if (balance < 0) { this.lastError = USER_BAD_ACCOUNT_DATA; return(false); } this.userBalance = balance; int cnt = MAX_RETRY_CNT; do { if (user.refreshDevice() && writeTransactionData(user, -1, balance, accountData)) { break; } } while (--cnt > 0); return(false); } }
/// <summary> /// <P>Verifies user's authentication response. User is "authenticated" if /// and only if the digital signature generated the user iButton matches /// the digital signature generated by the coprocessor after the user's /// unique secret has been recreated on the coprocessor.</P> /// /// <P><B>Flow of action: </B> /// <ul> /// <li> Generate 3-byte "challenge" on coprocessor </li> /// <li> Write challenge to scratchpad of user </li> /// <li> Read account data page with signature </li> /// <li> Attempt to match user's signature with the coprocessor </li> /// </ul></P> /// </summary> /// <param name="user"> SHAiButtonUser upon which the transaction occurs. /// </param> /// <seealso cref= SHAiButtonCopr#generateChallenge(int,byte[],int) </seealso> /// <seealso cref= SHAiButtonCopr#verifyAuthentication(byte[],byte[],byte[],byte[],byte) </seealso> /// <seealso cref= SHAiButtonUser#readAccountData(byte[],int,byte[],int,byte[],int) </seealso> /// <seealso cref= #getLastError() </seealso> public override bool verifyUser(SHAiButtonUser user) { lock (this) { //clear any error this.lastError = SHATransaction.NO_ERROR; //local vars byte[] fullBindCode = this.verifyUser_fullBindCode; byte[] scratchpad = this.verifyUser_scratchpad; byte[] accountData = this.verifyUser_accountData; byte[] mac = this.verifyUser_mac; byte[] chlg = this.verifyUser_chlg; int wcc; //Generate random challenge. This must be done on the //coprocessor, otherwise flags aren't setup for VALIDATE_PAGE. if (!copr.generateChallenge(0, chlg, 0)) { lastError = COPR_COMPUTE_CHALLENGE_FAILED; return(false); } //have user answer the challenge wcc = user.readAccountData(chlg, 0, accountData, 0, mac, 0); if (wcc < 0) { if (user.hasWriteCycleCounter()) { // failed to read account data this.lastError = SHATransaction.USER_READ_AUTH_FAILED; return(false); } Array.Copy(ffBlock, 0, scratchpad, 8, 4); } else { //copy the write cycle counter into scratchpad Convert.toByteArray(wcc, scratchpad, 8, 4); } //get the user's fullBindCode, formatted for user device user.getFullBindCode(fullBindCode, 0); //get the user address and page num from fullBindCode Array.Copy(fullBindCode, 4, scratchpad, 12, 8); //set the same challenge bytes Array.Copy(chlg, 0, scratchpad, 20, 3); OneWireEventSource.Log.Debug("------------------------------------"); OneWireEventSource.Log.Debug("Verifying user"); OneWireEventSource.Log.Debug("chlg: " + Convert.toHexString(chlg)); OneWireEventSource.Log.Debug("accountData: " + Convert.toHexString(accountData)); OneWireEventSource.Log.Debug("mac: " + Convert.toHexString(mac)); OneWireEventSource.Log.Debug("wcc: " + user.WriteCycleCounter); OneWireEventSource.Log.Debug("fullBindCode: " + Convert.toHexString(fullBindCode)); OneWireEventSource.Log.Debug("scratchpad: " + Convert.toHexString(scratchpad)); OneWireEventSource.Log.Debug("------------------------------------"); if (!copr.verifyAuthentication(fullBindCode, accountData, scratchpad, mac, user.AuthorizationCommand)) { this.lastError = SHATransaction.COPROCESSOR_FAILURE; return(false); } return(true); } }
/// <summary> /// <P>Verifies user's account data. Account data is "verified" if and /// only if the account balance is greater than zero and the digital /// signature matches the signature recreated by the coprocessor.</P> /// /// <P><B>Flow of action: </B> /// <ul> /// <li> Read the account data from user </li> /// <li> Extract account balance </li> /// <li> Debit money from balance </li> /// <li> Insert the new balance </li> /// <li> Reset the digital signature </li> /// <li> Use coprocessor to sign account data </li> /// <li> Insert the new digital signature </li> /// <li> Write the account data to the user </li> /// </ul></P> /// /// <P>If previous steps have been executed, all "Read" commands on /// the user are reading from cached data.</P> /// </summary> /// <param name="user"> SHAiButtonUser upon which the transaction occurs. /// </param> /// <returns> <code>true</code> if and only if the account balance is /// greater than zero and digital signature matches the signature /// recreated by the coprocessor. /// </returns> /// <seealso cref= SHAiButtonUser#readAccountData(byte[],int) </seealso> /// <seealso cref= SHAiButtonCopr#verifySignature(byte[],byte[],byte[]) </seealso> /// <seealso cref= #getLastError() </seealso> public override bool verifyTransactionData(SHAiButtonUser user) { lock (this) { //clear any error this.lastError = NO_ERROR; //init local vars byte[] scratchpad = this.verifyData_scratchpad; byte[] accountData = this.verifyData_accountData; byte[] verify_mac = this.verifyData_mac; //if verifyUser was called, this is a read of cached data int wcc = user.WriteCycleCounter; //if verifyUser was called, this is a read of cached data user.readAccountData(accountData, 0); //get the user's balance from accountData int balance = Convert.toInt(accountData, I_BALANCE, 3); if (balance < 0) { this.lastError = USER_BAD_ACCOUNT_DATA; return(false); } this.userBalance = balance; //get the mac from the account data page Array.Copy(accountData, I_SIGNATURE, verify_mac, 0, 20); //now lets reset the mac copr.getInitialSignature(accountData, I_SIGNATURE); //and reset the CRC accountData[I_FILE_CRC16 + 0] = 0; accountData[I_FILE_CRC16 + 1] = 0; //now we also need to get things like wcc, user_page_number, user ID if (wcc < 0) { if (user.hasWriteCycleCounter()) { // failed to read account data this.lastError = USER_READ_AUTH_FAILED; return(false); } //has no write cycle counter Array.Copy(ffBlock, 0, scratchpad, 8, 4); } else { //copy the write cycle counter into scratchpad Convert.toByteArray(wcc, scratchpad, 8, 4); } scratchpad[12] = (byte)user.AccountPageNumber; user.getAddress(scratchpad, 13, 7); copr.getSigningChallenge(scratchpad, 20); if (!copr.verifySignature(accountData, scratchpad, verify_mac)) { this.lastError = COPROCESSOR_FAILURE; return(false); } return(true); } }
/// <summary> /// Does the writing of transaction data to the user button as well /// as actually signing the data with the coprocessor. /// </summary> private bool writeTransactionData(SHAiButtonUser user, int transID, int balance, byte[] accountData) { //init local vars SHAiButtonCopr copr = this.copr; int acctPageNum = user.AccountPageNumber; byte[] scratchpad = this.writeTransactionData_scratchpad; // length of the TMEX file - 28 data, 1 cont. ptr accountData[I_FILE_LENGTH] = (byte)29; // transaction ID - 2 data bytes accountData[I_TRANSACTION_ID + 0] = (byte)transID; accountData[I_TRANSACTION_ID + 1] = (byte)((int)((uint)transID >> 8)); // conversion factor - 2 data bytes accountData[I_CONVERSION_FACTOR + 0] = 0x8B; accountData[I_CONVERSION_FACTOR + 1] = 0x48; // account balance - 3 data bytes Convert.toByteArray(balance, accountData, I_BALANCE, 3); // initial signature - 20 data bytes copr.getInitialSignature(accountData, I_SIGNATURE); // data type code - dynamic: 0x00, static: 0x01 accountData[I_DATA_TYPE_CODE] = 0x01; // continuation pointer for TMEX file accountData[I_CONTINUATION_PTR] = 0x00; // clear out the crc16 - 2 data bytes accountData[I_FILE_CRC16 + 0] = 0x00; accountData[I_FILE_CRC16 + 1] = 0x00; //we need to increment the writeCycleCounter since we will be writing to the part int wcc = user.WriteCycleCounter; if (wcc > 0) { //copy the write cycle counter into scratchpad Convert.toByteArray(wcc + 1, scratchpad, 8, 4); } else { if (user.hasWriteCycleCounter()) { // failed to read account data this.lastError = SHATransaction.USER_READ_AUTH_FAILED; return(false); } Array.Copy(ffBlock, 0, scratchpad, 8, 4); } // svcPageNumber, followed by address of device scratchpad[12] = (byte)acctPageNum; user.getAddress(scratchpad, 13, 7); // copy in the signing challenge copr.getSigningChallenge(scratchpad, 20); // sign the data, return the mac right into accountData copr.createDataSignature(accountData, scratchpad, accountData, I_SIGNATURE); //after signature make sure to dump in the inverted CRC int crc = ~CRC16.compute(accountData, 0, accountData[I_FILE_LENGTH] + 1, acctPageNum); //set the the crc16 bytes accountData[I_FILE_CRC16 + 0] = (byte)crc; accountData[I_FILE_CRC16 + 1] = (byte)(crc >> 8); //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// OneWireEventSource.Log.Debug("------------------------------------"); OneWireEventSource.Log.Debug("writing transaction data"); OneWireEventSource.Log.Debug("acctPageNum: " + acctPageNum); OneWireEventSource.Log.Debug("accountData: " + Convert.toHexString(accountData)); OneWireEventSource.Log.Debug("------------------------------------"); //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// // write it to the button try { if (user.writeAccountData(accountData, 0)) { return(true); } } catch (OneWireException owe) { OneWireEventSource.Log.Debug(owe.ToString()); } this.lastError = SHATransaction.USER_WRITE_DATA_FAILED; return(false); }
//private byte[] executeTransaction_scratchpad = new byte[32]; /// <summary> /// <P>Performs the signed debit, subtracting the debit amount from /// the user's balance and storing the new, signed account data on the /// user's iButton. The debit amount can be set using /// <code>transaction.setParameter(SHADebit.DEBIT_AMOUNT, 50)</code>, /// where the value is in units of cents (i.e. for 1 dollar, use 100).</P> /// /// <P><B>Flow of action: </B> /// <ul> /// <li> Read the account data from user </li> /// <li> Extract account balance </li> /// <li> Reset the digital signature </li> /// <li> Use coprocessor to sign account data </li> /// <li> Insert the new digital signature </li> /// <li> Write the account data to the user </li> /// </ul></P> /// /// <P>If previous steps have been executed, all "Read" commands on /// the user are reading from cached data.</P> /// </summary> /// <param name="user"> SHAiButtonUser upon which the transaction occurs. </param> /// <param name="verifySuccess"> A bool to let this method know if verification /// was successful. /// </param> /// <returns> <code>true</code> if and only if the user has enough in the /// account balance to perform the requested debit AND a new digital /// signature is successfully created AND the account data has been written /// to the button. /// </returns> /// <seealso cref= SHAiButtonUser#readAccountData(byte[],int) </seealso> /// <seealso cref= SHAiButtonUser#writeAccountData(byte[],int) </seealso> /// <seealso cref= SHAiButtonCopr#createDataSignature(byte[],byte[],byte[],int) </seealso> /// <seealso cref= #getLastError() </seealso> public override bool executeTransaction(SHAiButtonUser user, bool verifySuccess) { lock (this) { //clear any error this.lastError = NO_ERROR; //init local vars //holds the working copy of account data byte[] accountData = this.executeTransaction_accountData; //holds the backup copy of account data before writing byte[] oldAcctData = this.executeTransaction_oldAcctData; //holds the account data read back for checking byte[] newAcctData = this.executeTransaction_newAcctData; //if verifyUser was called, this is a read of cached data user.readAccountData(accountData, 0); //before we update the account data array at all, let's make a backup copy Array.Copy(accountData, 0, oldAcctData, 0, 32); //get the user's verification data Array.Copy(accountData, I_VERDATA, this.ver_data, 0, 7); bool success = writeTransactionData(user, this.master_ver_data, accountData); //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) { bool dataOK = false; int cnt = MAX_RETRY_CNT; do { //calling verify user re-issues a challenge-response //and reloads the cached account data in the user object. if (verifyUser(user)) { //compare the user's account data against the working //copy and the backup copy. if (user.readAccountData(newAcctData, 0)) { bool isOld = true; bool isCur = true; for (int i = 0; i < 32 && (isOld || isCur); i++) { //match the backup isOld = isOld && (newAcctData[i] == oldAcctData[i]); //match the working copy isCur = isCur && (newAcctData[i] == accountData[i]); } if (isOld) { //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; } else if (isCur) { dataOK = true; } else { //iBUTTON DATA IS TOTALLY HOSED //keep trying to get account data on the button success = writeTransactionData(user, this.ver_data, accountData); } } } } while (!dataOK && ((cnt -= 1) > 0)); //TODO if (!dataOK) { //couldn't fix the data after 255 retries IOHelper.writeLine("Catastrophic Failure!"); success = false; } } return(success); } }
/// <summary> /// <P>Performs the transaction. For any given transaction type, /// this step would involve updating any necessary account data, /// signing the account data using the coprocessor's system signing /// secret, and writing the updated account data to the user /// iButton</P> /// </summary> public abstract bool executeTransaction(SHAiButtonUser user, bool verifySuccess);
/// <summary> /// <P>Verifies account data is valid for this service. The user's /// account data is recreated on the coprocessor and signed using /// the system signing secret. If the recreated signature matches /// the signature in the account data, the account data is valid.</P> /// </summary> public abstract bool verifyTransactionData(SHAiButtonUser user);
/// <summary> /// <P>Verifies that SHAiButtonUser is a valid user of this service. /// This step writes a three byte challenge to the SHAiButtonUser /// before doing an authenticated read of the account data. The /// returned MAC is verified using the system authentication secret /// on the coprocessor. If the MAC matches that generated by the /// coprocessor, this function returns true.</P> /// </summary> public abstract bool verifyUser(SHAiButtonUser user);
/// <summary> /// <P>Setups initial transaction data on SHAiButtonUser. This step /// creates the account data file, signs it with the coprocessor, /// and writes it to the iButton.</P> /// </summary> public abstract bool setupTransactionData(SHAiButtonUser user);