package org.irmacard.personalisation;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Observable;
import java.util.Properties;
import java.util.Random;
import java.util.Scanner;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.irmacard.credentials.Attributes;
public class CardWriter extends Observable {
private static final String MUTIL_LOG_LOCATION = "mutil_log.txt";
private static final String LOAD_SCRIPT_LOCATION = "load_applet.txt";
private static final String DELETE_SCRIPT_LOCATION = "delete_applet.txt";
private static final String MUTIL_LOCATION = "MUtil/MUtil.exe";
private static Random rand;
private int progress = 0;
private Properties config;
static {
rand = new Random();
}
public CardWriter(Properties config) {
this.config = config;
}
public void Write(Card card) throws Exception {
setProgress(5);
byte[] credentialPin = new byte[4];
for(int i = 0; i < 4; i++) {
credentialPin[i] = (byte) (rand.nextInt(10) + 0x30);
}
byte[] cardPin = new byte[6];
for(int i = 0; i < 6; i++) {
cardPin[i] = (byte) (rand.nextInt(10) + 0x30);
}
try{
DatabaseConnection.setCardStatusPersonalized(card.getCardId(), config); //Start with db, so we won't have unsaved personalized cards. Will commit when card is personalized.
setProgress(15);
loadApplet();
setProgress(40);
IrmaIssuer issuer = new IrmaIssuer();
Attributes attributes = new Attributes();
attributes.add("userID", card.getUserID().getBytes());
attributes.add("securityHash", hash(card.getUserID(), card.getCardId()));
issuer.issue(attributes);
issuer.setPin(credentialPin);
setProgress(80);
//TODO: set credential pin
card.setPersonalised();
setProgress(90);
sendMail(cardPin, credentialPin, card);
setProgress(100);
}
catch(Exception e) {
try {
DatabaseConnection.rollback();
}
catch (Exception ex) {
ex.printStackTrace();
Logger.log("Error while rolling back", ex);
}; //Throw original exception
throw e;
}
DatabaseConnection.commit(); //Only save the changed status if personalization actually worked
}
private byte[] hash(String... strings) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
for(String string : strings) {
digest.update(string.getBytes());
}
return digest.digest();
}
private void loadApplet() throws IOException, InterruptedException, MUtilException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(new String[]{MUTIL_LOCATION, LOAD_SCRIPT_LOCATION, MUTIL_LOG_LOCATION});
proc.waitFor();
Scanner scan = new Scanner(new File(MUTIL_LOG_LOCATION));
String line = scan.nextLine();
while(scan.hasNextLine()) {
line = scan.nextLine();
}
scan.close();
//It is not only OK if the applet is succesfully loaded, but also if it was already on the card
if(!line.equals("Line 3:;Loaded") && !line.endsWith("Duplicate AID")) {
throw new MUtilException(line);
}
}
public static void clearCard() throws IOException, InterruptedException, MUtilException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(new String[]{MUTIL_LOCATION, DELETE_SCRIPT_LOCATION, MUTIL_LOG_LOCATION});
proc.waitFor();
Scanner scan = new Scanner(new File(MUTIL_LOG_LOCATION));
String line = scan.nextLine();
while(scan.hasNextLine()) {
line = scan.nextLine();
}
scan.close();
//It is not only OK if the applet is succesfully deleted, but also if it was never on the card
if(!line.equals("Line 3:;Deleted") && !line.endsWith("Application Not Loaded")) {
throw new MUtilException(line);
}
}
private void sendMail(byte[] cardPin, byte[] credentialPin, Card card) throws MessagingException, FileNotFoundException, IOException {
String cardPinString = new String(cardPin);
String credentialPinString = new String(credentialPin);
Properties props = System.getProperties();
props.put("mail.smtp.host", config.getProperty("smtpServer"));
Session session = Session.getDefaultInstance(props);
Message msg = new MimeMessage(session){
@Override
protected void updateMessageID() throws MessagingException {
super.updateMessageID();
String messageId = getMessageID();
String fromAdres = config.getProperty("fromAdres");
setHeader("Message-ID", messageId.substring(0, messageId.indexOf('@')) + fromAdres.substring(fromAdres.indexOf('@')));
}
};
try {
msg.setFrom(new InternetAddress(config.getProperty("fromAdres")));
} catch (AddressException e) {
e.printStackTrace();
}
msg.setRecipient(Message.RecipientType.TO, new InternetAddress(card.getEmail()));
msg.setSubject(config.getProperty("mailSubject"));
msg.setText(config.getProperty("mailBody").replace("$NAME$", card.getName()).replace("$CARD_PIN$", cardPinString).replace("$CREDENTIAL_PIN$", credentialPinString));
msg.setSentDate(new Date());
Transport.send(msg);
}
private void setProgress(int progress) {
this.progress = progress;
setChanged();
notifyObservers();
}
public int getProgress() {
return progress;
}
public static class MUtilException extends Exception {
private static final long serialVersionUID = 0x9288f90e2cfb7723L;
public MUtilException(String message) {
super(message);
}
}
}