//---------------------------------------------------------------------------
// 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.
//---------------------------------------------------------------------------
//
// shaibutton.c - Protocol-level functions as well as useful utility
//                functions for sha applications.
//
// Version: 2.10
//

#include "ownet.h"
#include "shaib.h"

//Global
SMALLINT in_overdrive[MAX_PORTNUM];

//---------------------------------------------------------------------
// Uses File I/O API to find the coprocessor with a specific
// coprocessor file name.  Usually 'COPR.0'.
//
// 'copr'   - Structure for holding coprocessor information
// 'fe'     - pointer to file entry structure, with proper filename.
//
// Returns: TRUE, found a valid coprocessor
//          FALSE, no coprocessor is present
//
SMALLINT FindCoprSHA(SHACopr* copr, FileEntry* fe)
{
   SMALLINT FoundCopr = FALSE;
   int data;

   // now get all the SHA iButton parts until we find
   // one that has the right file on it.
   if(FindNewSHA(copr->portnum, copr->devAN, TRUE))
   {
      do
      {
         short handle;

         if(owOpenFile(copr->portnum, copr->devAN, fe, &handle))
         {
            int length = fe->NumPgs << 5;
            uchar* raw = malloc(length*5);

            if(owReadFile(copr->portnum, copr->devAN,
                          handle, raw,
                          length, &length))
            {
               if(!owCloseFile(copr->portnum, copr->devAN, handle))
                  return FALSE;
               if(raw != 0)
                  data = GetCoprFromRawData(copr, raw, length);
               FoundCopr = TRUE;
            }

            free(raw);
         }
      }
      while(!FoundCopr && FindNewSHA(copr->portnum, copr->devAN, FALSE));
   }

   return FoundCopr;
}

//---------------------------------------------------------------------
// Extracts coprocessor configuration information from raw data.
// Returns the last unaccessed index.
//
// 'copr'   - Structure for holding coprocessor information
// 'raw'    - Raw bytes, usually from file stored on disk or on the
//            coprocessor itself.
// 'length' - The length of coprocessor data.
//
int GetCoprFromRawData(SHACopr* copr, uchar* raw, int length)
{
   int i, namelen, siglen, auxlen;
   //copy in the name of the user's service file
   memcpy(copr->serviceFilename, raw, 5);

   //various page numbers
   copr->signPageNumber = raw[5];
   copr->authPageNumber = raw[6];
   copr->wspcPageNumber = raw[7];

   //version information
   copr->versionNumber = raw[8];

   //skip 4 bytes for date info;
   //get bind data, bind code, and signing challenge
   memcpy(copr->bindData, &raw[13], 32);
   memcpy(copr->bindCode, &raw[45], 7);
   memcpy(copr->signChlg, &raw[52], 3);

   namelen = raw[55];
   siglen = raw[56];
   auxlen = raw[57];

   // read in provider name as null-terminated string
   copr->providerName = malloc(namelen+1);
   memcpy(copr->providerName, &raw[58], namelen);
   copr->providerName[namelen] = '\0';

   // get initial signature
   memcpy(copr->initSignature, &raw[58+namelen],
            (siglen>20?20:siglen) );

   // read in auxilliary data as null-terminated string
   copr->auxilliaryData = malloc(auxlen+1);
   memcpy(copr->auxilliaryData,
            &raw[58+namelen+siglen], auxlen);
   copr->auxilliaryData[auxlen] = '\0';

   //encryption code
   i = 58+namelen+siglen+auxlen;
   copr->encCode = raw[i];

   //ds1961S compatibility flag - optional
   if((length>i+1) && (raw[i+1]!=0))
      copr->ds1961Scompatible = TRUE;
   else
      copr->ds1961Scompatible = FALSE;

   return i+2;
}

//---------------------------------------------------------------------
// Uses File I/O API to find the user token with a specific
// service file name.  Usually 'DSLM.102'.
//
// 'user'       - Structure for holding user token information
// 'fe'         - pointer to file entry structure, with proper
//                service filename.
// 'doBlocking' - if TRUE, method blocks until a user token is found.
//
// Returns: TRUE, found a valid user token
//          FALSE, no user token is present
//
SMALLINT FindUserSHA(SHAUser* user, FileEntry* fe,
                     SMALLINT doBlocking)
{
   SMALLINT FoundUser = FALSE;

   for(;;)
   {
      // now get all the SHA iButton parts until we find
      // one that has the right file on it.
      while(!FoundUser && FindNewSHA(user->portnum, user->devAN, FALSE))
      {
         short handle;

         if(owOpenFile(user->portnum, user->devAN, fe, &handle))
         {
            user->accountPageNumber = fe->Spage;
            FoundUser = TRUE;
            if(!owCloseFile(user->portnum, user->devAN, handle))
               return FALSE;
         }
      }

      if(FoundUser)
         return TRUE;
      else if(!doBlocking)
         return FALSE;
   }
}

//final buffer for tracking list of known SHA parts
//only holds the CRC for each part
static uchar ListOfKnownSHA[MAX_PORTNUM][MAX_SHA_IBUTTONS];
static int listIndex = 0;
//intermediate buffer for tracking list of known SHA parts
static uchar listBuffer[MAX_PORTNUM][MAX_SHA_IBUTTONS];
static int indexBuffer = 0;
//holds last state of FindNewSHA
static SMALLINT needsFirst = FALSE;

//---------------------------------------------------------------------
// Finds new SHA iButtons on the given port.  Uses 'triple-buffer'
// technique to insure it only finds 'new' buttons.
//
// 'portnum'     - number 0 to MAX_PORTNUM-1.  This number is provided to
//                 indicate the symbolic port number.
// 'devAN'       - pointer to buffer for device address
// 'resetList'   - if TRUE, final buffer is cleared.
//
// Returns: TRUE, found a new SHA iButton.
//          FALSE, no new buttons are present.
//
SMALLINT FindNewSHA(int portnum, uchar* devAN, SMALLINT resetList)
{
   uchar ROM[8];
   uchar tempList[MAX_SHA_IBUTTONS] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
                                       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
   int tempIndex = 0, i;
   SMALLINT hasDevices = TRUE, completeList = FALSE;

   // force back to standard speed
   if(MODE_NORMAL != owSpeed(portnum,MODE_NORMAL))
   {
      OWERROR(OWERROR_LEVEL_FAILED);
      return FALSE;
   }

   in_overdrive[portnum&0x0FF] = FALSE;

   // get first device in list with the specified family
   if(needsFirst || resetList)
   {
      hasDevices = owFirst(portnum, TRUE, FALSE);
      completeList = TRUE;
      if(resetList)
         listIndex = 0;
   }
   else
   {
      hasDevices = owNext(portnum, TRUE, FALSE);
   }

   if(hasDevices)
   {
      do
      {
         // verify correct device type
         owSerialNum(portnum, ROM, TRUE);
         tempList[tempIndex++] = ROM[7];

         // compare crc to complete list of ROMs
         for(i=0; i<listIndex; i++)
         {
            if(ROM[7] == ListOfKnownSHA[portnum&0x0FF][i])
               break;
         }

         // found no match;
         if(i==listIndex)
         {
            // check if correct type and not copr_rom
            if ((SHA_FAMILY_CODE   == (ROM[0] & 0x7F)) ||
                (SHA33_FAMILY_CODE == (ROM[0] & 0x7F)))
            {
               // save the ROM to the return buffer
               owSerialNum(portnum,devAN,TRUE);
               ListOfKnownSHA[portnum&0x0FF][listIndex++] = devAN[7];
               return TRUE;
            }
         }
      }
      while(owNext(portnum, TRUE, FALSE));
   }

   // depleted the list, start over from beginning next time
   needsFirst = TRUE;

   if(completeList)
   {
      // known list is triple-buffered, listBuffer is intermediate
      // buffer.  tempList is the immediate buffer, while
      // ListOfKnownSHA is the final buffer.
      if( (memcmp(tempList, listBuffer[portnum&0x0FF], MAX_SHA_IBUTTONS)==0) &&
          tempIndex == indexBuffer )
      {
         memcpy(ListOfKnownSHA[portnum&0x0FF], tempList, MAX_SHA_IBUTTONS);
         listIndex = tempIndex;
      }
      else
      {
         memcpy(listBuffer[portnum&0x0FF], tempList, MAX_SHA_IBUTTONS);
         indexBuffer = tempIndex;
      }

   }
   return FALSE;
}


//-------------------------------------------------------------------------
// Select the current device and attempt overdrive if possible.  Usable
// for both DS1963S and DS1961S.
//
// 'portnum'     - number 0 to MAX_PORTNUM-1.  This number is provided to
//                 indicate the symbolic port number.
//
// Return: TRUE - device selected
//         FALSE - device not select
//
SMALLINT SelectSHA(int portnum)
{
   int rt,cnt=0;

#ifdef __MC68K__
   // Used in when overdrive isn't...Used owVerify for overdrive
   rt = owAccess(portnum);
#else
   //\\//\\//\\//\\//\\//\\//\\//\\//\\//
   #ifdef DEBUG_DUMP
      uchar ROM[8];
      int i, mcnt;
      char msg[255];

      owSerialNum(portnum,ROM, TRUE);
      mcnt = sprintf(msg,"\n  Device select ");
      for (i = 0; i < 8; i++)
         mcnt += sprintf(msg + mcnt, "%02X",ROM[i]);
      mcnt += sprintf(msg + mcnt,"\n");
      printf("%s",msg);
   #endif
   //\\//\\//\\//\\//\\//\\//\\//\\//\\//

   // loop to access the device and optionally do overdrive
   do
   {
      rt = owVerify(portnum,FALSE);

      // check not present
      if (rt != 1)
      {
         // if in overdrive, drop back
         if (in_overdrive[portnum&0x0FF])
         {
            // set to normal speed
            if(MODE_NORMAL == owSpeed(portnum,MODE_NORMAL))
            	in_overdrive[portnum&0x0FF] = FALSE;
         }
      }
      // present but not in overdrive
      else if (!in_overdrive[portnum&0x0FF])
      {
         // put all devices in overdrive
         if (owTouchReset(portnum))
         {
            if (owWriteByte(portnum,0x3C))
            {
               // set to overdrive speed
               if(MODE_OVERDRIVE == owSpeed(portnum,MODE_OVERDRIVE))
               		in_overdrive[portnum&0x0FF] = TRUE;
            }
         }

         rt = 0;
      }
      else
         break;
   }
   while ((rt != 1) && (cnt++ < 3));
#endif

   return rt;
}


//-------------------------------------------------------------------------
// Creates a random challenge using the SHA engine of the given
// coprocessor.
//
// 'copr'      - Structure for holding coprocessor information.
// 'pagenum'   - pagenumber to use for calculation.
// 'chlg'      - 3-byte return buffer for challenge.
// 'offset'    - offset into resulting MAC to pull challenge data from.
//
// Return: TRUE - create challenge succeeded.
//         FALSE - an error occurred.
//
SMALLINT CreateChallenge(SHACopr* copr, SMALLINT pageNum,
                         uchar* chlg, SMALLINT offset)
{
   uchar scratchpad[32];
   int   addr = pageNum << 5;
   uchar es = 0x1F;
   int   start = 8;

   if(offset>0 && offset<17)
      start += offset;

   //set the serial number
   owSerialNum(copr->portnum, copr->devAN, FALSE);

   OWASSERT( EraseScratchpadSHA18(copr->portnum, addr, FALSE),
             OWERROR_ERASE_SCRATCHPAD_FAILED, FALSE );

   OWASSERT( SHAFunction18(copr->portnum, SHA_COMPUTE_CHALLENGE, addr, TRUE),
             OWERROR_SHA_FUNCTION_FAILED, FALSE );

   OWASSERT( ReadScratchpadSHA18(copr->portnum, &addr, &es, scratchpad, TRUE),
             OWERROR_READ_SCRATCHPAD_FAILED, FALSE );

   memcpy(chlg,&scratchpad[start],3);

   return TRUE;
}

//-------------------------------------------------------------------------
// Answers a random challenge by performing an authenticated read of the
// user's account information.
//
// 'user'      - Structure for holding user token information.
// 'chlg'      - 3-byte buffer of challenge data.
//
// Return: the value of the write cycle counter for the account page.
//         or -1 if there is an error
//
int AnswerChallenge(SHAUser* user, uchar* chlg)
{
   int addr = user->accountPageNumber << 5;

   user->writeCycleCounter = -1;
   memcpy(&user->accountFile[20], chlg, 3);

   //set the serial number
   owSerialNum(user->portnum, user->devAN, FALSE);

   if(user->devAN[0]==0x18)
   {
      // for the DS1963S
      OWASSERT( EraseScratchpadSHA18(user->portnum, addr, FALSE),
                OWERROR_ERASE_SCRATCHPAD_FAILED, -1 );

      OWASSERT( WriteScratchpadSHA18(user->portnum, addr,
                                     user->accountFile, 32,
                                     TRUE),
                 OWERROR_WRITE_SCRATCHPAD_FAILED, -1 );

      user->writeCycleCounter =
         ReadAuthPageSHA18(user->portnum,
                           user->accountPageNumber,
                           user->accountFile,
                           user->responseMAC, TRUE);
   }
   else if(user->devAN[0]==0x33||user->devAN[0]==0xB3)
   {
      // for the DS1961S
      OWASSERT( WriteScratchpadSHA33(user->portnum, addr,
                                     &user->accountFile[16],
                                     FALSE),
                 OWERROR_WRITE_SCRATCHPAD_FAILED, -1 );

      user->writeCycleCounter =
         ReadAuthPageSHA33(user->portnum,
                           user->accountPageNumber,
                           user->accountFile,
                           user->responseMAC, TRUE);
   }
   return user->writeCycleCounter;
}

//-------------------------------------------------------------------------
// Verifies the authentication response of a user token.
//
// 'copr'      - Structure for holding coprocessor information.
// 'user'      - Structure for holding user token information.
// 'chlg'      - 3-byte buffer of challenge data.
// '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, the user's authentication response matched exactly the
//            signature generated by the coprocessor.
//         If FALSE, an error occurred or the signature did not match.
//
SMALLINT VerifyAuthResponse(SHACopr* copr, SHAUser* user,
                            uchar* chlg, SMALLINT doBind)
{
   int addr = copr->wspcPageNumber << 5;
   int wcc = user->writeCycleCounter;
   uchar sign_cmd;
   uchar scratchpad[32];
   uchar fullBindCode[15];

   memset(scratchpad, 0x00, 32);
   memset(fullBindCode, 0x0FF, 15);

   // format the bind code properly
   if(user->devAN[0]==0x18)
   {
      // Format for DS1963S
      memcpy(fullBindCode, copr->bindCode, 4);
      memcpy(&fullBindCode[12], &(copr->bindCode[4]), 3);
      // use ValidateDataPage command
      sign_cmd = SHA_VALIDATE_DATA_PAGE;
      // copy wcc LSB first
      if(!IntToBytes(&scratchpad[8], 4, wcc))
         OWERROR(OWERROR_NO_ERROR_SET);
   }
   else if(user->devAN[0]==0x33||user->devAN[0]==0xB3)
   {
      // Leave bindCode FF for DS1961S
      // Use AuthenticateHost command
      sign_cmd = SHA_AUTHENTICATE_HOST;
      // the user doesn't have a write cycle counter
      memset(&scratchpad[8], 0x0FF, 4);
   }
   else
   {
      OWERROR(OWERROR_WRONG_TYPE);
      return FALSE;
   }

   // the pagenumber
   fullBindCode[4] = (uchar)user->accountPageNumber;
   // and 7 bytes of the address of current device
   memcpy(&fullBindCode[5], user->devAN, 7);

   // get the user address and page num from fullBindCode
   memcpy(&scratchpad[12], &fullBindCode[4], 8);
   // set the same challenge bytes
   memcpy(&scratchpad[20], chlg, 3);

   // set the serial number to that of the coprocessor
   owSerialNum(copr->portnum, copr->devAN, FALSE);

   // install user's unique secret on the wspc secret
   if(doBind)
   {
      OWASSERT( BindSecretToiButton18(copr->portnum,
                                      copr->authPageNumber,
                                      copr->wspcPageNumber&7,
                                      copr->bindData, fullBindCode, FALSE),
                OWERROR_BIND_SECRET_FAILED, FALSE );
      if(user->devAN[0]==0x33||user->devAN[0]==0xB3)
      {
         // also copy the resulting secret into secret location 0, to
         // replace the signing secret.  Necessary for producing the
         // DS1961S's write-authorization MAC.
         OWASSERT( CopySecretSHA18(copr->portnum, 0),
                   OWERROR_COPY_SECRET_FAILED, FALSE);
      }
   }

   // recreate the signature and verify
   OWASSERT( WriteDataPageSHA18(copr->portnum, copr->wspcPageNumber,
                              user->accountFile, doBind),
              OWERROR_WRITE_DATA_PAGE_FAILED, FALSE );

   OWASSERT( WriteScratchpadSHA18(copr->portnum, addr, scratchpad, 32, TRUE),
              OWERROR_WRITE_SCRATCHPAD_FAILED, FALSE );

   OWASSERT( SHAFunction18(copr->portnum, sign_cmd, addr, TRUE),
             OWERROR_SHA_FUNCTION_FAILED, FALSE );

   OWASSERT( MatchScratchpadSHA18(copr->portnum,
                                user->responseMAC, TRUE),
             OWERROR_MATCH_SCRATCHPAD_FAILED, FALSE );

   return TRUE;
}

//-------------------------------------------------------------------------
// Creates a data signature for the given data buffer using the hardware
// coprocessor.
//
// 'copr'          - Structure for holding coprocessor information.
// 'data'          - data written to the data page to sign
// 'scratchpad'    - data written to the scratchpad to sign
// 'signature'     - data buffer which is either holding the signature that
//                   must match exactly what is generated on the coprocessor
//                   -or- will hold the resulting signature created by the
//                   coprocessor.
// 'readSignature' - implies whether or not the signature buffer
//                   receives the contents of the scratchpad or is used to
//                   match the contents of the scratchpad.  If true,
//                   scratchpad contents are read into the signature buffer.
//
// Return: If TRUE, the user's authentication response matched exactly the
//            signature generated by the coprocessor or the signature was
//            successfully copied into the return buffer.
//         If FALSE, an error occurred or the signature did not match.
//
SMALLINT CreateDataSignature(SHACopr* copr, uchar* data,
                             uchar* scratchpad, uchar* signature,
                             SMALLINT readSignature)
{
   int addr = copr->signPageNumber << 5;

   // set the serial number to that of the coprocessor
   owSerialNum(copr->portnum, copr->devAN, FALSE);

   OWASSERT( WriteDataPageSHA18(copr->portnum, copr->signPageNumber,
                              data, FALSE),
              OWERROR_WRITE_DATA_PAGE_FAILED, FALSE );

   OWASSERT( WriteScratchpadSHA18(copr->portnum, addr,
                                scratchpad, 32, TRUE),
              OWERROR_WRITE_SCRATCHPAD_FAILED, FALSE );

   OWASSERT( SHAFunction18(copr->portnum, SHA_SIGN_DATA_PAGE,
                         addr, TRUE),
             OWERROR_SHA_FUNCTION_FAILED, FALSE );

   if(readSignature)
   {
      OWASSERT( ReadScratchpadSHA18(copr->portnum, 0, 0,
                                  scratchpad, TRUE),
                 OWERROR_READ_SCRATCHPAD_FAILED, FALSE );
   }
   else
   {
      OWASSERT( MatchScratchpadSHA18(copr->portnum,
                                   signature, TRUE),
                 OWERROR_MATCH_SCRATCHPAD_FAILED, FALSE );
   }

   memcpy(signature, &scratchpad[8], 20);

   return TRUE;
}


//-------------------------------------------------------------------------
// Decipher the variable Val into a 'len' byte array to set
// LSB First
//
SMALLINT IntToBytes(uchar* byteArray, int len, unsigned int val)
{
   int i = 0;
   for (; i<len; i++)
   {
      byteArray[i] = (uchar)val;
      val >>= 8;
   }

   if(val==0)
      return TRUE;
   else
      return FALSE;
}

//-------------------------------------------------------------------------
// Decipher the range 'len' of the byte array to an integer
// LSB First
//
int BytesToInt(uchar* byteArray, int len)
{
   unsigned int val = 0;
   int i = (len - 1);
   // Concatanate the byte array into one variable.
   for (; i >= 0; i--)
   {
      val <<= 8;
      val |= (byteArray[i] & 0x00FF);
   }
   return val;
}
