/// <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);
            }
        }
Example #6
0
        /// <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);
            }
        }
Example #7
0
        /// <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);
        }
Example #8
0
        //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);