/*
 * --------------------------------------------------------------------------------------------------------------------
 * Example sketch/program showing how to read and modify data from a Mifare Ultralight C PICC 
 * --------------------------------------------------------------------------------------------------------------------
 *
 * This code enables basic communication with a Mifare Ultralight C PICC.
 * It allows reading and writing of data including basic operations for password protection. 
 * 
 * This sketch imlements NO measures for page blocking and using OTP and the upwarts counter
 * 
 * This sketch should run stable on a Arduinno Uno with RC522 module. 
 * But the code itself is very sensitive for modifications due to my bad programming 
 * or stability issues with the used libraries. If you change / optimize the code, be 
 * prepared for some weird complications
 * 
 * have fun!
 * 
 * @license Released into the public domain.
 * 
 * use
 * - auth 49454D4B41455242214E4143554F5946    for authentication (with standard key)
 * - newKey 49454D4B41455242214E4143554F5946  for writing a new key (in this case the standard key) to the PICC. 
 * - dunp                                     to list the content of the PICC (data might not visible due to read-protection)
 * - wchar 10 hello world                     to write "hello world" starting at page 10
 * - whex 10 0123456789ABC                    to write HEX values starting at page 10 
 * - protect 25                               to protect the page from 25 upward
 * - setpbit 1                                to set the protection to write-protected
 * 
 * 
 * Typical pin layout used:
 * -----------------------------------------------------------------------------------------
 *             MFRC522      Arduino       Arduino   Arduino    Arduino          Arduino
 *             Reader/PCD   Uno/101       Mega      Nano v3    Leonardo/Micro   Pro Micro
 * Signal      Pin          Pin           Pin       Pin        Pin              Pin
 * -----------------------------------------------------------------------------------------
 * RST/Reset   RST          9             5         D9         RESET/ICSP-5     RST
 * SPI SS      SDA(SS)      10            53        D10        10               10
 * SPI MOSI    MOSI         11 / ICSP-4   51        D11        ICSP-4           16
 * SPI MISO    MISO         12 / ICSP-1   50        D12        ICSP-1           14
 * SPI SCK     SCK          13 / ICSP-3   52        D13        ICSP-3           15
 */
 
/* 
 * IMPORTANT!!!
 * in order to be able to read Mifare Ultralight C PICC the typical RC522-module needs to be modified.
 * L1 and L2 must be exchanged for better inductors (e.g. FERROCORE CW1008-2200). Otherwise this code will not work! 
 */

/* 
 *  The Mifare Ultralight C encrypted authentification process can be found here: https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
 */

#include <SPI.h>
#include <MFRC522.h>
#include <DES.h>

#define RST_PIN          9          // Configurable, see typical pin layout above
#define SS_PIN           10         // Configurable, see typical pin layout above

DES des;

MFRC522 mfrc522(SS_PIN, RST_PIN);  // Create MFRC522 instance
MFRC522::StatusCode status;


//3DES-Key (This is the standard key according to the NPX specification)
byte C_key[] = { 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, 0x46, 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42};                
              
uint64_t IV=0;
boolean isPresent=0;
boolean isEncrypted=0;
boolean isUlC=0;
char Scmd[28];
char Sdata[64];

// byte page=0;

byte buffer[24];
boolean isLocked[255];                                // which Pages are locked to read only
byte startProtect=255;                                // starting from which page, the data is protectet by the key
boolean access;                                       // how is the data protected: 0=write protection (data visible) 1=read/write protection (data = 0x00)

void setup() {
  Serial.begin(115200);                               // Initialize serial communications with the PC
  while (!Serial);                                    // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
  des.init(C_key,IV);
  SPI.begin();                                        // Init SPI bus
  mfrc522.PCD_Init();                                 // Init MFRC522
  mfrc522.PCD_DumpVersionToSerial();                  // Show details of PCD - MFRC522 Card Reader details
  Serial.println(F("Read/Write Mifare Ultralight C"));
}


void loop() {
int i=0;
boolean IsCommand=1;
char x;

  if(Serial.available() > 0){
    // Serial.println(freeRam());
    while(i<40){
      while(!Serial.available());
      x=Serial.read();
      if(IsCommand)
        Scmd[i]=x;              
      else
        Sdata[i]=x;
      if(x == '\n'){                            // newline
        if(IsCommand){
          Scmd[i]=0x00;                         // proper Ending 
          i=41;                                 // exit
        }
        else{
          Sdata[i]=0x00;
          i=41;
        }
      }
      else if(x == ' '){
        if(IsCommand==1){
          IsCommand=0;
          Scmd[i]=0x00;
          i=0;
        }
        else
          i++;
      }
      else
        i++;
    }
    if(!isPresent)
      Serial.println(F("No PICC available!"));
    else{  
      if(!strcmp(Scmd,"dump")){                           // dump data 
        DumpDataUltralight();                             
      }
      else if(!strcmp(Scmd,"auth")){
        RequestAuthUltralightC();
      }
      else if(!strcmp(Scmd,"newKey")){
        newKey();
      }
      else if (!strcmp(Scmd,"wchar")){
        writeData(0);
      }
      else if (!strcmp(Scmd,"whex")){
        writeData(1);
      }
      else if (!strcmp(Scmd,"protect")){
        protect(1);
      }
      else if (!strcmp(Scmd,"setpbit")){
        protect(0);
      }
    }
    Serial.flush();
    Scmd[0]=0x00;
    Sdata[0]=0x00;
  }
  // Look for new cards
  if(!isPresent){
    if (!mfrc522.PICC_IsNewCardPresent()) {
      return;
    }
   
    // Select one of the cards
    if (!mfrc522.PICC_ReadCardSerial()) {
      return;
    }
    else{
      isPresent=1;
      if(mfrc522.uid.sak == 0x00){
        if(checkIfUltralight()){
          Serial.println (F("Mifare Ultralight C PICC found!")); 
          getPageInfo();
          isUlC=1;
        }
        else{
          Serial.println (F("Other Mifare Ultralight compatible PICC found"));
          isUlC=0;
        }
        Serial.print(F("UID: "));
        dumpInfo(mfrc522.uid.uidByte, mfrc522.uid.size);
      }
      else{
        Serial.println (F("Other Card found, not compatible!"));
        mfrc522.PICC_HaltA();
        isPresent=0;
        return;
      }
    }
  }
  if(isPresent){                              // test read - it it fails, the PICC is most likely gone
    byte byteCount = sizeof(buffer);
    status = mfrc522.MIFARE_Read(0, buffer, &byteCount);
    if(status!=mfrc522.STATUS_OK){
      isPresent=0;
      isEncrypted=0;
      mfrc522.PCD_StopCrypto1(); 
      Serial.println("Card gone...");
    }
  }
}



/*
 * This small routine starts a request for encryption. 
 * for details see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
 */
void RequestAuthUltralightC(void){
int i;
  byte AuthBuffer[24] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};            // 
  byte AuthLength=24;   
  byte RndA[8]={0,0,0,0,0,0,0,0};
  byte RndB[8]={0,0,0,0,0,0,0,0};                                               // decrypted RndB
  byte rRndB[8]={0,0,0,0,0,0,0,0};                                                  // rotated RndB
  byte encRndA[8]={0,0,0,0,0,0,0,0};                                                // encrypted RndA' from the PICC
  byte dRndA[8]={0,0,0,0,0,0,0,0};                                                  // decrypted RndA
  byte message[24]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};               // Message to transfer
  byte iv_ar[8];                                                                    // this is the starting IV for decryption
  byte byteCount=sizeof(buffer);

  if(isEncrypted){
    Serial.println(F("PICC already authenticated"));
    return;
  }
  // sparse and set the key
  if(!setAuthKey()){
    Serial.println(F("key could not be set"));
    return;
  }
  // set IV to 0x00
  for(i=0 ; i<8 ; i++){
    iv_ar[i] = 0x00;
  }
  memcpy(&IV,iv_ar,8);                      // set IV 
  des.set_IV(IV);
  
  // Build command buffer
  AuthBuffer[0] = 0x1A; // CMD_3DES_AUTH -> Ultralight C 3DES Authentication.
  AuthBuffer[1] = 0x00; // 

  // Calculate CRC_A
  status = mfrc522.PCD_CalculateCRC(AuthBuffer, 2, &AuthBuffer[2]);
  if (status != mfrc522.STATUS_OK) {
    return;
  }

  AuthLength=sizeof(AuthBuffer);

  // Transmit the buffer and receive the response, validate CRC_A.
  status = mfrc522.PCD_TransceiveData(AuthBuffer, 4, AuthBuffer, &AuthLength, NULL, 0, true);
  if (status != mfrc522.STATUS_OK) {
    Serial.println("Ultralight C Auth failed");
    Serial.println(mfrc522.GetStatusCodeName(status));
    Serial.print(F("Reply: "));
    dumpInfo(AuthBuffer,AuthLength);
    return;
  }
  memcpy(message,AuthBuffer+1,8);           // copy the enc(RndB) from the message
  memcpy(iv_ar,message,8);                  // use enc(RndB) as new IV for the next encryption.
  
  des.set_size(8);
  des.tdesCbcDecipher(message,RndB);        // decrypt enc(RndB) -> now we have RndB

  randomSeed(AuthBuffer[1]);                // create RndA 
  int number;
  for(int i=0; i<8; i++){
    number=random(255);
    RndA[i]=number&0xFF;                     // 
  }

  memcpy(rRndB, RndB ,8);                   // copy RndB
  rol(rRndB,8);                             // create RndB'
  memcpy(message,RndA,8);                   // copy RndA in the first part of message
  memcpy(message+8, rRndB ,8);              // adding RndB'
  
  AuthBuffer[0] = 0xAF;                     // set the PCD-command

  memcpy(&IV,iv_ar,8);                      // set IV = to enk(RndB)
  des.set_IV(IV);                           
  des.set_size(16);
  des.tdesCbcEncipher(message, &AuthBuffer[1]);
    
  status = mfrc522.PCD_CalculateCRC(AuthBuffer, 17, &AuthBuffer[17]);
  if (status != mfrc522.STATUS_OK) {
    return;
  }
  
  memcpy(&IV,&AuthBuffer[9],8);             // set IV to decrypt reply from PICC enc(RndA') -> RndA 
  des.set_IV(IV);     
    
  status = mfrc522.PCD_TransceiveData(AuthBuffer, 19, AuthBuffer, &AuthLength, NULL, 0, true);
  if (status != mfrc522.STATUS_OK) {
    Serial.print(F("Auth failed failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    Serial.println(F("Reply: "));
    dumpInfo(AuthBuffer,AuthLength);
    return;
  }
  else{                               
    if(AuthBuffer[0]==0x00){                            // reply from PICC should start with 0x00
      memcpy(encRndA, &AuthBuffer[1],8);                // copy enc(RndA')
      des.set_size(8);
      des.tdesCbcDecipher(encRndA,dRndA);               // decrypt now we have decrypted RndA'
      rol(RndA,8);                                      // rotate orgiginal RndA to RndA'
      for(i=0; i<8; i++){                               // compare it
        if(RndA[i] != dRndA[i]){
          i=9;
        }
      }
      if(i==8){
        Serial.println(F("Keys are correct :-)")); 
        isEncrypted=1;
      }
      else
        Serial.println(F("Keys do not match"));
    }
    else{
      Serial.println(F("Wrong answer!!!"));
    }
  }  
  return;
} 


byte DumpDataUltralight(void){
byte page=0;
  
  for(byte i=0; i<255; i++)                                // initialize lock bit table
    isLocked[i]=0;
  byte Count = sizeof(buffer);

  Serial.println(F("Page lock auth 0  1  2  3"));
  getPageInfo();                                          // get information about acess
  status = mfrc522.MIFARE_Read(page, buffer, &Count);
  while((status==mfrc522.STATUS_OK)){                     // This loop stops at an NAK-answer
    Serial.print(page);
    if(page<10)
      Serial.print(F(" "));
    Serial.print(F("   "));
    if(isLocked[page])
      Serial.print(F(" x   "));
    else
      Serial.print(F("     "));
    if(page>=startProtect){
      if(access)
        Serial.print(F(" w   "));
      else
        Serial.print(F("r/w  "));    
    }
    else
      Serial.print(F("     "));
    dumpInfo(buffer,4);
    page++;
    status = mfrc522.MIFARE_Read(page, buffer, &Count);
  }
  Count = sizeof(buffer);
  status=mfrc522.PICC_WakeupA(buffer, &Count);            // needed to wake up the card after receiving a NAK-answer
  return page;
}


boolean checkIfUltralight(void){
byte Count=sizeof(buffer);  

  if(mfrc522.MIFARE_Read(43, buffer, &Count)==mfrc522.STATUS_OK){
    if(mfrc522.MIFARE_Read(44, buffer, &Count)==mfrc522.STATUS_OK){
      return 0;
    }  
    else{
      Count = sizeof(buffer);
      status=mfrc522.PICC_WakeupA(buffer, &Count);            // needed to wake up the card after receiving a NAK-answer
      return 1;
    }
  }  
  Count = sizeof(buffer);
  status=mfrc522.PICC_WakeupA(buffer, &Count);                // needed to wake up the card after receiving a NAK-answer
  Serial.println(mfrc522.GetStatusCodeName(status));
  return 0;    
}


boolean getPageInfo(){  
  int i;
  uint32_t mask=0;     
  byte bsize = sizeof(buffer);
  status = mfrc522.MIFARE_Read(2, buffer, &bsize);            // read the first lock bits
  if(status==mfrc522.STATUS_OK){
    mask=buffer[3];
    mask=mask<<8;
    mask=mask+buffer[2];
    isLocked[0]=1;
    isLocked[1]=1;
    isLocked[2]=1;

    if(mask&0x0001){
     isLocked[3]=1; 
    }  
    mask=mask>>1;

    if(mask&0x0001){
     for(i=0;i<6;i++)
      isLocked[4+i]=1;
    }  
    mask=mask>>1;

    if(mask&0x0001){
     for(i=0;i<6;i++)
      isLocked[10+i]=1;
    }  
    mask=mask>>1;

    for(i=0;i<13;i++){
      if(mask&0x0001)
        isLocked[3+i]=1;
      mask=mask>>1;
    }
  }
  else{
    bsize = sizeof(buffer);
    status=mfrc522.PICC_WakeupA(buffer, &bsize);            // needed to wake up the card after receiving a NAK-answer
    return 0;
  }
  status = mfrc522.MIFARE_Read(40, buffer, &bsize);
  if(status==mfrc522.STATUS_OK){
    mask=buffer[1];
    mask=mask<<8;
    mask=mask+buffer[0];
    int j=16;
    for(i=0; i<8; i++){
      if((i%4)==0){
        if(mask&0x0001)
          setBooleanBits(isLocked+j, 12);
      }  
      else{
        if(mask&0x0001)
          setBooleanBits(isLocked+j, 4);
        j=j+4;
      }
      mask=mask>>1;
    }

 
    for(i=0;i<3;i++){
      if(mask&0x0001)
        isLocked[41+i]=1;
      mask=mask>>1;
    }
    
    if(mask&0x0001)
      setBooleanBits(isLocked+44, 4);
    mask=mask>>1;
      
    for(i=0;i<3;i++){
      if(mask&0x0001)
        isLocked[41+i]=1;
      mask=mask>>1;
    }
    
    if(mask&0x0001)
      setBooleanBits(isLocked+44, 4);
    mask=mask>>1;

  }
  else{
    bsize = sizeof(buffer);
    status=mfrc522.PICC_WakeupA(buffer, &bsize);            // needed to wake up the card after receiving a NAK-answer
    return 0;
  }
  status = mfrc522.MIFARE_Read(42, buffer, &bsize);
  if(status==mfrc522.STATUS_OK){
    startProtect=buffer[0];   
  }
  else{
    bsize = sizeof(buffer);
    status=mfrc522.PICC_WakeupA(buffer, &bsize);            // needed to wake up the card after receiving a NAK-answer
    return 0;
  }
  status = mfrc522.MIFARE_Read(43, buffer, &bsize);
  if(status==mfrc522.STATUS_OK){
    access=buffer[0]&0x01;   
  }
  else{
    bsize = sizeof(buffer);
    status=mfrc522.PICC_WakeupA(buffer, &bsize);            // needed to wake up the card after receiving a NAK-answer
    return 0;
  }
  return 1;
}

void setBooleanBits(boolean *ar, int len){
  for(int i=0; i<len; i++)
    ar[i]=1;
}


void protect(bool mode){
byte value=0;
byte page;  
  if(mode)                                      // set start page of protection
    page=42;
  else                                          // set write or read/write protection
    page=43;  
  
  if(isLocked[page]){
    Serial.println(F("PICC is write protected"));
    return;
  }
  if((startProtect<page)&&(!isEncrypted)){
    Serial.print(F("page ")); 
    Serial.print(page); 
    Serial.println(F(" is password protected, autehticate first"));
    return;
  }  
  
  value = atoi(Sdata);

  if(mode){
    if(value>48)
      value=48;
  }
  else{
    if(value>1)
      value=1;
  }
  
  for(byte i=0;i<4;i++)
    buffer[i]=0;
  
  buffer[0]=value;
  
  status = mfrc522.MIFARE_Ultralight_Write(page, buffer, 4);
  if(status==mfrc522.STATUS_OK){
    if(mode){
      Serial.print(F("password protection starts now at page: "));
      Serial.println(value);
    }
    else{
      Serial.print(F("protection bit is set to: "));
      if(value)
        Serial.println(F("write protected"));
      else
        Serial.println(F("read/write protected"));
    }
  }
  else{
    if(mode)
      Serial.print(F("ERROR: password protection not set: "));
    else
      Serial.print(F("ERROR: protection bit not set: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
  }
}


boolean setAuthKey(void){
int i;
  for(i=0;i<32;i++){
    if(Sdata[i]==0x00){
      Serial.println(F("Key too short! (needs to be 32 digits)"));
      return 0;
    }
    else{
      if((i%2==0)){
        C_key[i/2]=char2byte(&Sdata[i]);
      }
    }
  }
  for(i=0;i<8;i++){
    C_key[i+16]=C_key[i];
  }
  des.change_key(C_key);
  return 1;
}


boolean newKey(void){
byte i,j,pos; 
byte pos_ar[4]={7,3,15,11}; 
byte Buffer[4];
byte page=0; 
    
  if(!isEncrypted){
    Serial.println(F("please authenticate fist"));
    return 0;
  }
  if(setAuthKey()){
    for(j=0;j<4;j++){
      page=0x2C+j;
      pos=pos_ar[j];
      for(i=0;i<4;i++){
        Buffer[i]=C_key[pos-i];  
      }
      status = mfrc522.MIFARE_Ultralight_Write(page, Buffer, 4);
    }
    if(status==mfrc522.STATUS_OK){
      Serial.println(F("new key is written to the card!"));
    }
    else{ 
      Serial.print(F("new key could not be written: "));  
      Serial.println(mfrc522.GetStatusCodeName(status));
    // restart the PICC
      mfrc522.PICC_IsNewCardPresent();              // is required, as the PICC remains silent after a NAK
      mfrc522.PICC_ReadCardSerial();
    }
  }
  else
    return 0;
    
  return 1;
}



void writeData(boolean mode){
byte page=0;
byte i=0;
byte Buffer[4]={0,0,0,0};

  if(isUlC)
    Serial.println(F("Ultralight C"));
  else
    Serial.println(F("not a Ultralight C PICC!"));  
  
  while((Sdata[i]!=' ')&&(i<3)){
    page = page * 10;
    page = page + Sdata[i]-48;
    i++;
  }
  i++;                                  // to overcome the ' '
   
  if(page<4){
    Serial.println(F("no user memory here"));
    return;
  }
  if(isUlC&&(page>39)){
    Serial.println(F("no user memory here"));
    return;
  }
  byte j=0;
  boolean done=0;
  for(; page<40; page++){
    for(j=0;j<4;j++){
      if(Sdata[i]==0x00){
        while(j<4){
          Buffer[j]=0;
          j++;
        }
        done=1;
      }
      else{
        if(mode){
          Buffer[j]=char2byte(Sdata+i);
          i=i+2;
        }  
        else{
          Buffer[j]=Sdata[i];
          i++;
        }
      }
    }
    
    status = mfrc522.MIFARE_Ultralight_Write(page, Buffer, 4);
    Serial.print(F("writing page "));
    Serial.print(page);
    Serial.println(mfrc522.GetStatusCodeName(status));
    if(status != mfrc522.STATUS_OK){
      return;  
    }
    if(done)
      return;
  }
}



// Needed to create RndB' out of RndB
void rol(byte *data, int len){
    byte first = data[0];
    for (int i = 0; i < len-1; i++) {
        data[i] = data[i+1];
    }
    data[len-1] = first;
}



void dumpInfo(byte *ar, int len){
  for(int i=0 ; i<len ; i++){
    if(ar[i]<0x10)
      Serial.print(F("0"));
    Serial.print(ar[i], HEX);
    Serial.print(F(" "));
  }
  Serial.println("");
}



byte char2byte(char *s){
byte x = 0;
  for(int i=0; i <2 ; i++) {
    char c = *s;
    if (c >= '0' && c <= '9') {
      x *= 16;
      x += c - '0';
    }
    else if (c >= 'A' && c <= 'F') {
      x *= 16;
      x += (c - 'A') + 10;
    }
    else if (c >= 'a' && c <= 'f') {
      x *= 16;
      x += (c - 'a') + 10;
    }
    s++;
  }
  return x;
}


/*
 * If the hex key is: "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F" 
 * then you have to write the sequence "07 06 05 04 03 02 01 00 0F 0E 0D 0C 0B 0A 09 08" in 4 pages, 
 * from page 0x2C (44) to page 0x2F (47).
 * 
 * if you want to change where (which pages) the authentication is required, here is how to do that:
 * 0x2A defines the page address from which the authentication is required. E.g. if 0x2A = 0x30 no authentication is needed all as memory goes up to 0x2F.
 * 0x2B defines if authentication is required for read/write (0x2B=0) or only for write access (0x2B=1)

On example of Key1 = 0001020304050607h and Key2 = 08090A0B0C0D0E0Fh, 
the command sequence needed for key programming with WRITE command is:
•A2 2C 07 06 05 04 CRC
•A2 2D 03 02 01 00 CRC
•A2 2E 0F 0E 0D 0C CRC
•A2 2F 0B 0A 09 08 CRC

*/
