/*
 * File:	UpdateKey.Java
 * Purpose:	To update the System Key (EF3f01)
 * Date:	07.05.2001
 * Author:	Mohammed Sadiq (mohammed.sadiq@gemplus.com)
 *
 */
 
// Java imports
import java.io.*;
import java.util.*;

// OCF imports
import opencard.core.service.*;
import opencard.core.terminal.*;
import opencard.opt.util.*;
import opencard.core.util.*;
import opencard.opt.terminal.UserInteraction;
import com.gemplus.opencard.terminal.*;

// Cryptix imports
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.DESKeySpec;

public class UpdateKey {
  
  static PassThruCardService myCardService = null;
  
  public static void main(String[] args) {
    ExtendedCommandAPDU commandToSend = null;
    ResponseAPDU        responseFromCard = null;

    // true if you need the debug info, false otherwise.
    boolean deb = true;

    int i;
    byte Trnd[] = new byte[8];
    byte cardResponse[] = new byte[12];
    byte cardCryptogram[] = new byte[4];
    byte cardRandom[] = new byte[8];
        
    // The program assumes that the card has a default system key.
    // i.e, "TEST KEYTEST KEY"
    byte systemKey[] = {(byte)0x54, (byte)0x45, (byte)0x53, (byte)0x54, (byte)0x20, (byte)0x4B, (byte)0x45, (byte)0x59, 
                        (byte)0x54, (byte)0x45, (byte)0x53, (byte)0x54, (byte)0x20, (byte)0x4B, (byte)0x45, (byte)0x59};
        
    // Change this to the value you want the system key to be.
    // It is "TESTKEY1TESTKEY2" in the program
    byte newKey[] = {(byte)0x54, (byte)0x45, (byte)0x53, (byte)0x54, (byte)0x4B, (byte)0x45, (byte)0x59, (byte)0x31,
                     (byte)0x54, (byte)0x45, (byte)0x53, (byte)0x54, (byte)0x4B, (byte)0x45, (byte)0x59, (byte)0x32};                             
    
    byte Kats[] = new byte[16];
    byte tempBlock[] = new byte[8];
    byte CRYCKS8[] = new byte[8];
    
    byte idEFkey[] = {(byte)0x3F, (byte)0x01};        
    byte keyType, keyVersion;
    byte data12[] = new byte[8];
    byte data34[] = new byte[8];
    byte dataField1[] = new byte[8];
    byte dataField2[] = new byte[8];
    byte dataUpdateKey[] = new byte[16];    
    byte cks = 0x00;
    
    try {

      // Init OCF
      SmartCard.start();
      System.out.print("\nSearching for a smartcard... ");
      
      CardRequest cr = new CardRequest(CardRequest.ANYCARD, null, null);
      SmartCard   myCard = SmartCard.waitForCard(cr);
      System.out.println("FOUND\n");

      myCardService = (PassThruCardService)myCard.getCardService(PassThruCardService.class, true);
                  
      // You have to do a GetInfo to get the values of Key Type & Key Version      
      System.out.println("***** Select EF (0x3F01) *****");
      commandToSend = new ExtendedCommandAPDU((byte)0x00, (byte)0xA4, (byte)0x02, (byte)0x0C, idEFkey);
      System.out.println("Command: " + commandToSend.toString());
      responseFromCard = myCardService.sendCommandAPDU(commandToSend);
      System.out.println("Response: " + responseFromCard.toString());
      
      System.out.println("***** GetInfo *****");
      commandToSend = new ExtendedCommandAPDU((byte)0x80, (byte)0xC0, (byte)0x02, (byte)0x05, (byte)0x08);
      System.out.println("Command: " + commandToSend.toString());
      responseFromCard = myCardService.sendCommandAPDU(commandToSend);
      System.out.println("Response: " + responseFromCard.toString());
      
      // Retrieve the card response; fetch KeyType and KeyVersion.
      cardResponse = responseFromCard.data();                  
      keyType = cardResponse[0];
      keyVersion = cardResponse[2];
      
      // Compute the checksum 
      for(i=0; i<8; i++)
        cks ^= newKey[i];
      cks ^= keyType;
      cks ^= keyVersion;            
      cks ^= (byte)0xFF;  // Invert the checksum
            
      // Populate data12
      data12[0] = keyType;
      data12[1] = 0;
      data12[2] = keyVersion;
      data12[3] = cks;
      Util.arrayCopy(newKey, 0, data12, 4, 4);      
      
      // P1, P2 for the first key will be 0x00, 0x00(provided the file is selected
      // Note: It will be 0x00, 0x03 for the second key
      byte p1UpdateKey = (byte)0x00; // offset MSB
      byte p2UpdateKey = (byte)0x00; // offset LSB
      
      // Populate data34
      Util.arrayCopy(newKey, 4, data34, 0, 4);
      data34[4] = p1UpdateKey;
      data34[5] = p2UpdateKey;
      
      // Compute the sum of the above 14 bytes      
      int intSum = 0;
      for(i=0; i<8; i++)        
        intSum += Util.toInt(data12[i]);        
      for(i=0; i<6; i++)
        intSum += Util.toInt(data34[i]);                              
      
      // Append the sum of 14 bytes as the 15th & 16th byte
      data34[6] = (byte)(intSum/256);
      data34[7] = (byte)(intSum%256);      
      
      // Terminal random to be used in SELFK
      for(i=0; i<8; i++)
        Trnd[i] = (byte)(i+1);
      
      System.out.println("\n***** SelFK *****");
      commandToSend = new ExtendedCommandAPDU((byte)0x80, (byte)0x28, (byte)0x00, (byte)0x01, Trnd, (byte)0x0C);
      System.out.println("Command: " + commandToSend.toString());
      responseFromCard = myCardService.sendCommandAPDU(commandToSend);
      System.out.println("Response: " + responseFromCard.toString());
      
      // Retrieve the card response
      cardResponse = responseFromCard.data();
      
      // Extract the Card Cryptogram(4 bytes) & Card Random(8 bytes)
      for(i=0; i<4; i++)
        cardCryptogram[i] = cardResponse[i];
      for(i=0; i<8; i++)
        cardRandom[i] = cardResponse[i+4];
               
      // Compute the temporary administration key
      // Kats = 3DES_16(RN, K)
      // Where RN is the Card Random Number, and K is the System Key
      Kats = Util.computeSessionKey(cardRandom, systemKey);
      
      dataField1 = Util.doDecrypt(data12, Kats);
      dataField2 = Util.doDecrypt(data34, Kats);
      
      Util.arrayCopy(dataField1, 0, dataUpdateKey, 0, 8);
      Util.arrayCopy(dataField2, 0, dataUpdateKey, 8, 8);
      
      System.out.println("\n***** Update Binary (first half of the key) *****");
      commandToSend = new ExtendedCommandAPDU((byte)0x04, (byte)0xD6, p1UpdateKey, p2UpdateKey, dataUpdateKey);
      System.out.println("Command: " + commandToSend.toString());
      responseFromCard = myCardService.sendCommandAPDU(commandToSend);
      System.out.println("Response: " + responseFromCard.toString());
            
      //// For the second half of the key ////
      cks = 0;
      // Compute the checksum 
      for(i=0; i<8; i++)
        cks ^= newKey[i+8];
      cks ^= keyType;
      cks ^= keyVersion;           
      cks ^= (byte)0xFF;  // Invert the checksum            
      
      // Populate data12
      data12[0] = keyType;
      data12[1] = 0;
      data12[2] = keyVersion;
      data12[3] = cks;
      Util.arrayCopy(newKey, 8, data12, 4, 4);      
      
      p1UpdateKey = (byte)0x00; // offset MSB
      p2UpdateKey = (byte)0x03; // offset LSB
      
      // Populate data34
      Util.arrayCopy(newKey, 12, data34, 0, 4);
      data34[4] = p1UpdateKey;
      data34[5] = p2UpdateKey;
      
      intSum=0;
      // Compute the sum of the above 14 bytes            
      for(i=0; i<8; i++)        
        intSum += Util.toInt(data12[i]);
      for(i=0; i<6; i++)                   
        intSum += Util.toInt(data34[i]);
      data34[6] = (byte)(intSum/256);
      data34[7] = (byte)(intSum%256);      
      
      dataField1 = Util.doDecrypt(data12, Kats);
      dataField2 = Util.doDecrypt(data34, Kats);
      
      Util.arrayCopy(dataField1, 0, dataUpdateKey, 0, 8);
      Util.arrayCopy(dataField2, 0, dataUpdateKey, 8, 8);
            
      System.out.println("\n***** Update Binary (second half of the key) *****");
      commandToSend = new ExtendedCommandAPDU((byte)0x04, (byte)0xD6, p1UpdateKey, p2UpdateKey, dataUpdateKey);
      System.out.println("Command: " + commandToSend.toString());
      responseFromCard = myCardService.sendCommandAPDU(commandToSend);
      System.out.println("Response: " + responseFromCard.toString());                             
                 
      SmartCard.shutdown();
      
    } catch(Exception e) {
      e.printStackTrace(System.out);
    }
    
  } // main()
    
} // class UpdateKey
