passwdmanager.hig.no.services.PasswdManager.java Source code

Java tutorial

Introduction

Here is the source code for passwdmanager.hig.no.services.PasswdManager.java

Source

package passwdmanager.hig.no.services;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.ejbca.cvc.CVCertificate;
import org.ejbca.cvc.CertificateParser;

import passwdmanager.hig.no.certificates.TerminalCVCertificateDirectory;
import passwdmanager.hig.no.lds.DG_14_FILE;
import passwdmanager.hig.no.lds.DG_15_FILE;
import passwdmanager.hig.no.lds.DG_1_FILE;
import passwdmanager.hig.no.lds.DG_COM;
import passwdmanager.hig.no.lds.DG_SOD;
import passwdmanager.hig.no.lds.DocumentSigner;
import passwdmanager.hig.no.lds.FileStructure;
import passwdmanager.hig.no.lds.SecurityObjectIndicator;
import passwdmanager.hig.no.lds.SecurityObjectIndicatorDG14;
import passwdmanager.hig.no.lds.SecurityObjectIndicatorDG15;
import passwdmanager.hig.no.lds.SimpleDocumentSigner;

import net.sourceforge.scuba.smartcards.CardFileInputStream;
import net.sourceforge.scuba.smartcards.CardServiceException;
import net.sourceforge.scuba.util.Hex;

/**
 * A class for encapsulating the whole passwd manager contents: the files, and
 * the encryption keys. Can be intialized (a) almost empty, (b) from the
 * DrivingLicenseService object (i.e. read in from the card), (c) form a ZIP
 * file containg the data groups.
 * 
 * 
 */
public class PasswdManager {

    private static final int BUFFER_SIZE = 243;

    private Map<Short, InputStream> rawStreams = new HashMap<Short, InputStream>();

    private Map<Short, InputStream> bufferedStreams = new HashMap<Short, InputStream>();

    private Map<Short, byte[]> filesBytes = new HashMap<Short, byte[]>();

    private Map<Short, Integer> fileLengths = new TreeMap<Short, Integer>();

    private Map<Short, Boolean> eapFlags = new TreeMap<Short, Boolean>();

    private int bytesRead = 0;

    private int totalLength = 0;

    // Our local copies of the COM and SOD files:
    private DG_COM comFile = null;

    private DG_SOD sodFile = null;

    private boolean eacSupport = false;

    private boolean eacSuccess = false;

    private CVCertificate cvcaCertificate = null;

    private PrivateKey eacPrivateKey = null;

    private PrivateKey aaPrivateKey = null;

    private DocumentSigner signer = null;

    private List<Short> eacFids = new ArrayList<Short>();

    private byte[] keySeed = null;

    private boolean updateCOMSODfiles = true;

    private BufferedInputStream preReadFile(BasicService service, short fid) throws CardServiceException {
        BufferedInputStream bufferedIn = null;
        if (rawStreams.containsKey(fid)) {
            int length = fileLengths.get(fid);
            bufferedIn = new BufferedInputStream(rawStreams.get(fid), length + 1);
            bufferedIn.mark(length + 1);
            rawStreams.put(fid, bufferedIn);
            return bufferedIn;
        } else {
            service.getFileSystem().selectFile(fid);
            CardFileInputStream cardIn = service.readFile();
            int length = cardIn.getFileLength();
            bufferedIn = new BufferedInputStream(cardIn, length + 1);
            totalLength += length;
            fileLengths.put(fid, length);
            bufferedIn.mark(length + 1);
            rawStreams.put(fid, bufferedIn);
            return bufferedIn;
        }
    }

    private void setupFile(BasicService service, short fid) throws CardServiceException {
        service.getFileSystem().selectFile(fid);
        CardFileInputStream in = service.readFile();
        int fileLength = in.getFileLength();
        in.mark(fileLength + 1);
        rawStreams.put(fid, in);
        totalLength += fileLength;
        fileLengths.put(fid, fileLength);
    }

    /**
     * Constructor. Reads in the data from the card service.
     * 
     * @param service
     *            the card service
     * @throws IOException
     *             on problems
     * @throws CardServiceException
     *             on problems
     */
    public PasswdManager(BasicService service) throws IOException, CardServiceException {
        this(service, null);
    }

    /**
     * Constructor. Reads in the data from the card service.
     * 
     * @param service
     *            the card service
     * @param documentNumber
     *            the document number to use for EAC, if not provided (null) the
     *            one from DG1 will be used.
     * @throws IOException
     *             on problems
     * @throws CardServiceException
     *             on problems
     */
    public PasswdManager(BasicService service, String documentNumber) throws IOException, CardServiceException {

        BufferedInputStream bufferedIn = null;

        bufferedIn = preReadFile(service, BasicService.EF_COM);
        comFile = new DG_COM(bufferedIn);
        bufferedIn.reset();

        String caRef = null;

        SecurityObjectIndicator[] indicators = comFile.getSOIArray();
        for (SecurityObjectIndicator indicator : indicators) {
            if (indicator instanceof SecurityObjectIndicatorDG14) {
                eacSupport = true;
                SecurityObjectIndicatorDG14 i = (SecurityObjectIndicatorDG14) indicator;
                caRef = new String(i.getCertificateSubjectId(), 1, i.getCertificateSubjectId()[0]);
                List<Integer> dgs = i.getDataGroups();
                for (Integer dg : dgs) {
                    eacFids.add(FileStructure.lookupFIDByTag(FileStructure.lookupTagByDataGroupNumber(dg)));
                    eapFlags.put(FileStructure.lookupFIDByTag(FileStructure.lookupTagByDataGroupNumber(dg)), true);
                }
            }
        }

        DG_14_FILE dg14file = null;
        for (int tag : comFile.getTagList()) {
            short fid = FileStructure.lookupFIDByTag(tag);
            if (fid == BasicService.EF_DG14) {
                bufferedIn = preReadFile(service, BasicService.EF_DG14);
                dg14file = new DG_14_FILE(bufferedIn);
                bufferedIn.reset();
            } else {
                if (!eacFids.contains(fid)) {
                    setupFile(service, fid);
                }
            }
        }
        bufferedIn = preReadFile(service, BasicService.EF_SOD);
        sodFile = new DG_SOD(bufferedIn);
        bufferedIn.reset();
        // Try to do EAC
        if (eacSupport) {
            List<CVCertificate> termCerts = null;
            PrivateKey termKey = null;
            TerminalCVCertificateDirectory d = TerminalCVCertificateDirectory.getInstance();
            if (caRef != null) {
                try {
                    List<CVCertificate> t = d.getCertificates(caRef);
                    if (t != null) {
                        termCerts = t;
                        termKey = d.getPrivateKey(caRef);
                    }
                } catch (NoSuchElementException nsee) {
                    nsee.printStackTrace();
                }
            }
            if (termCerts == null || termCerts.size() == 0) {
                // no luck, EAC present, but we don't have the certificates
                return;
            }
            // Try EAC
            if (documentNumber == null) {
                // Try DG1 if document number was not supplied
                bufferedIn = preReadFile(service, BasicService.EF_DG1);
                documentNumber = new DG_1_FILE(bufferedIn).getInfo().id;
                bufferedIn.reset();
            }

            // Map<Integer, PublicKey> cardKeys = dg14file.;
            Set<Integer> keyIds = dg14file.getIds();
            for (int i : keyIds) {
                try {
                    service.doEAC(i, dg14file.getKey(i), termCerts, termKey, documentNumber);
                    eacSuccess = true;
                    break;
                } catch (CardServiceException cse) {
                    cse.printStackTrace();
                }
            }
            if (eacSuccess) {
                for (Short fid : eacFids) {
                    setupFile(service, fid);
                }
            }
        }
    }

    /**
     * Constructor. Reads in the data from a ZIP file.
     * 
     * @param file
     *            the ZIP file
     * @throws IOException
     *             on problems
     */
    public PasswdManager(File file) throws IOException {
        this(file, true, null);
    }

    /**
     * Constructor. Reads in the from a ZIP file.
     * 
     * @param file
     *            the ZIP file
     * @param allowInconsistent whether the ZIP file can be incomplete (the missing data will be ignored)
     * @param docSigningPrivateKey private key to sign the data on the (SOD), can be null
     * @throws IOException
     *             on problems
     */
    public PasswdManager(File file, boolean allowInconsistent, DocumentSigner signer) throws IOException {
        this.signer = signer;
        ZipFile zipIn = new ZipFile(file);
        Enumeration<? extends ZipEntry> entries = zipIn.entries();
        List<ZipEntry> entryList = new ArrayList<ZipEntry>();
        boolean generateaa = false;
        boolean generateca = false;
        String signatureAlgorithm = "SHA256withRSA";
        X509Certificate docCertificate = null;
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            String fileName = entry.getName();
            if (fileName.equals("keyseed.bin")) {
                int size = (int) (entry.getSize() & 0x00000000FFFFFFFFL);
                if (size != 16) {
                    throw new IOException("Wrong key seed length in " + file);
                }
                keySeed = new byte[16];
                DataInputStream dataIn = new DataInputStream(zipIn.getInputStream(entry));
                dataIn.readFully(keySeed);
            } else if (fileName.equals("generateaa.key")) {
                generateaa = true;
            } else if (fileName.equals("generateca.key")) {
                generateca = true;
            } else if (fileName.equals("aaprivatekey.der")) {
                int size = (int) (entry.getSize() & 0x00000000FFFFFFFFL);
                byte[] keyData = new byte[size];
                DataInputStream dataIn = new DataInputStream(zipIn.getInputStream(entry));
                dataIn.readFully(keyData);
                try {
                    KeyFactory kf = KeyFactory.getInstance("RSA");
                    aaPrivateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(keyData));
                } catch (Exception ex) {
                    throw new IOException("Invalid RSA private key: " + ex.getMessage());
                }
            } else if (fileName.equals("caprivatekey.der")) {
                int size = (int) (entry.getSize() & 0x00000000FFFFFFFFL);
                byte[] keyData = new byte[size];
                DataInputStream dataIn = new DataInputStream(zipIn.getInputStream(entry));
                dataIn.readFully(keyData);
                try {
                    KeyFactory kf = KeyFactory.getInstance("ECDH");
                    eacPrivateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(keyData));
                } catch (Exception ex) {
                    throw new IOException("Invalid ECDH private key: " + ex.getMessage());
                }
            } else if (fileName.equals("cacert.cvcert")) {
                int size = (int) (entry.getSize() & 0x00000000FFFFFFFFL);
                byte[] data = new byte[size];
                DataInputStream dataIn = new DataInputStream(zipIn.getInputStream(entry));
                dataIn.readFully(data);
                try {
                    cvcaCertificate = (CVCertificate) CertificateParser.parseCertificate(data);
                } catch (Exception ex) {
                    throw new IOException("Invalid CVCA certificate: " + ex.getMessage());
                }
            } else if (fileName.equals("doccert.der")) {
                int size = (int) (entry.getSize() & 0x00000000FFFFFFFFL);
                byte[] data = new byte[size];
                DataInputStream dataIn = new DataInputStream(zipIn.getInputStream(entry));
                dataIn.readFully(data);
                try {
                    docCertificate = (X509Certificate) CertificateFactory.getInstance("X509")
                            .generateCertificate(new ByteArrayInputStream(data));
                    signer.setCertificate(docCertificate);
                } catch (Exception ex) {
                    throw new IOException("Invalid doc certificate: " + ex.getMessage());
                }
            } else if (fileName.equals("001D.bin") || fileName.equals("001E.bin")) {
                updateCOMSODfiles = false;
                entryList.add(entry);
            } else {
                entryList.add(entry);
            }
        }
        if (docCertificate == null && updateCOMSODfiles) {
            docCertificate = generateDummyCertificate(signatureAlgorithm);
        }
        for (ZipEntry entry : entryList) {
            String fileName = entry.getName();
            int size = (int) (entry.getSize() & 0x00000000FFFFFFFFL);
            try {
                boolean eapProtection = fileName.indexOf("eac") != -1;
                int delimIndex = eapProtection ? fileName.indexOf("eac") : fileName.indexOf('.');
                if (delimIndex != 4) {
                    System.out.println("DEBUG: skipping file " + fileName + "(delimIndex == " + delimIndex + ")");
                    continue;
                }
                short fid = Hex.hexStringToShort(fileName.substring(0, delimIndex));
                if (cvcaCertificate == null) {
                    eapProtection = false;
                }
                byte[] bytes = new byte[size];
                int fileLength = bytes.length;
                fileLengths.put(fid, fileLength);
                DataInputStream dataIn = new DataInputStream(zipIn.getInputStream(entry));
                dataIn.readFully(bytes);
                rawStreams.put(fid, new ByteArrayInputStream(bytes));
                eapFlags.put(fid, eapProtection);
                totalLength += fileLength;
                if (fid == BasicService.EF_COM) {
                    comFile = new DG_COM(new ByteArrayInputStream(bytes));
                } else if (fid == BasicService.EF_SOD) {
                    sodFile = new DG_SOD(new ByteArrayInputStream(bytes));
                }
            } catch (IOException ioe) {
            } catch (NumberFormatException nfe) {
                /* NOTE: ignore this file */
            }
        }
        try {
            if (updateCOMSODfiles && docCertificate == null) {
                throw new IOException("No SOD and no doc certificate provided.");
            }
            if (generateaa && aaPrivateKey != null) {
                throw new IOException("Both AA private key and request to generate one present.");
            }
            if (generateca && eacPrivateKey != null) {
                throw new IOException("Both CA private key and request to generate one present.");
            }
            if ((generateaa || generateca) && !updateCOMSODfiles) {
                throw new IOException("COM or SOD file present and request to generate private AA or CA keys.");
            }
            if (getFileList().contains(BasicService.EF_DG15) && aaPrivateKey == null) {
                throw new IOException("DG15 present, but no AA private key.");
            }
            if (getFileList().contains(BasicService.EF_DG14) && eacPrivateKey == null) {
                throw new IOException("DG14 present, but no CA private key.");
            }
        } catch (IOException ioe) {
            if (allowInconsistent) {
                updateCOMSODfiles = true;
                ioe.printStackTrace();
            } else {
                throw ioe;
            }
        }
        try {
            if (generateaa) {
                Provider provider = Security.getProvider("BC");
                KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", provider);
                generator.initialize(new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4));
                setAAKeys(generator.generateKeyPair());
            }
            if (generateca) {
                String preferredProvider = "BC";
                Provider provider = Security.getProvider(preferredProvider);
                KeyPairGenerator generator = KeyPairGenerator.getInstance("ECDH", provider);
                generator.initialize(new ECGenParameterSpec(PersoService.EC_CURVE_NAME));
                setEACKeys(generator.generateKeyPair());
            }
            if (comFile == null) {
                comFile = new DG_COM(1, 0, new ArrayList<Integer>(), null);
            }
            if (sodFile == null) {
                String digestAlgorithm = "SHA256";
                Map<Integer, byte[]> hashes = new HashMap<Integer, byte[]>();
                MessageDigest digest = MessageDigest.getInstance(digestAlgorithm);
                // HACK: two hashes are needed to construct a valid SOD file
                hashes.put(1, digest.digest(new byte[0]));
                hashes.put(2, digest.digest(new byte[0]));
                sodFile = new DG_SOD(digestAlgorithm, signatureAlgorithm, hashes, this.signer, docCertificate);
            }
            updateCOMSODFile(docCertificate);

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new IOException("Key generation failed: " + ex.getMessage());
        }

    }

    //FIXME some problems here, please use /certificates/CreateX509 to create DS certificates
    private X509Certificate generateDummyCertificate(String signatureAlgorithm) {
        try {
            Date today = Calendar.getInstance().getTime();
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(1024);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            Date dateOfIssuing = today;
            Date dateOfExpiry = today;
            X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
            certGenerator.setSerialNumber(new BigInteger("1"));
            certGenerator.setIssuerDN(new X509Name("C=NO, O=HIG, OU=CSCA, CN=PasswdManager/qingbao.guo@hig.no"));
            certGenerator.setSubjectDN(new X509Name("C=NO, O=HIG, OU=DSCA, CN=PasswdManager/qingbao.guo@hig.no"));
            certGenerator.setNotBefore(dateOfIssuing);
            certGenerator.setNotAfter(dateOfExpiry);
            certGenerator.setPublicKey(publicKey);
            certGenerator.setSignatureAlgorithm(signatureAlgorithm);
            X509Certificate cert = (X509Certificate) certGenerator.generate(privateKey, "BC");
            if (signer == null) {
                signer = new SimpleDocumentSigner(privateKey, cert);
            }
            return cert;
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * Constructs an alomost empty CARD with just the COM and SOD
     * files.
     * 
     * @throws GeneralSecurityException
     *             when ciphers or similar are not available.
     */
    public PasswdManager() throws GeneralSecurityException {

        /* EF.COM */
        List<Integer> tagList = new ArrayList<Integer>();
        comFile = new DG_COM(1, 0, tagList, null);

        byte[] comBytes = comFile.getEncoded();
        int fileLength = comBytes.length;
        totalLength += fileLength;
        fileLengths.put(BasicService.EF_COM, fileLength);
        rawStreams.put(BasicService.EF_COM, new ByteArrayInputStream(comBytes));

        /* EF.SOD */
        String signatureAlgorithm = "SHA256withRSA";
        //FIXME: some problems here, please use /certificates/CreateX509 to create DS certificates
        X509Certificate certificate = generateDummyCertificate(signatureAlgorithm);
        String digestAlgorithm = "SHA256";
        Map<Integer, byte[]> hashes = new HashMap<Integer, byte[]>();
        MessageDigest digest = MessageDigest.getInstance(digestAlgorithm);
        // HACK: two hashes are needed to construct a valid SOD file
        hashes.put(1, digest.digest(new byte[0]));
        hashes.put(2, digest.digest(new byte[0]));
        sodFile = new DG_SOD(digestAlgorithm, signatureAlgorithm, hashes, signer, certificate);
        byte[] sodBytes = sodFile.getEncoded();
        fileLength = sodBytes.length;
        totalLength += fileLength;
        fileLengths.put(BasicService.EF_SOD, fileLength);
        rawStreams.put(BasicService.EF_SOD, new ByteArrayInputStream(sodBytes));
    }

    /**
     * Gets an inputstream that is ready for reading.
     * 
     * @param fid
     * @return the input stream for reading
     */
    public synchronized InputStream getInputStream(final short fid) {
        try {
            InputStream in = null;
            byte[] file = filesBytes.get(fid);
            if (file != null) {
                /* Already completely read this file. */
                in = new ByteArrayInputStream(file);
                in.mark(file.length + 1);
            } else {
                /* Maybe partially read? Use the buffered stream. */
                in = bufferedStreams.get(fid); // FIXME: some thread may
                // already be reading this one?
                if (in != null && in.markSupported()) {
                    in.reset();
                }
            }
            if (in == null) {
                /* Not read yet. Start reading it. */
                startCopyingRawInputStream(fid);
                in = bufferedStreams.get(fid);
            }
            return in;
        } catch (IOException ioe) {
            ioe.printStackTrace();
            throw new IllegalStateException("ERROR: " + ioe.toString());
        }
    }

    /**
     * Starts a thread to read the raw inputstream.
     * 
     * @param fid
     * @throws IOException
     */
    public synchronized void startCopyingRawInputStream(final short fid) throws IOException {
        final PasswdManager dl = this;
        final InputStream unBufferedIn = rawStreams.get(fid);
        if (unBufferedIn == null) {
            throw new IOException("No raw inputstream to copy " + Integer.toHexString(fid));
        }
        final int fileLength = fileLengths.get(fid);
        unBufferedIn.reset();
        final PipedInputStream pipedIn = new PipedInputStream(fileLength + 1);
        final PipedOutputStream out = new PipedOutputStream(pipedIn);
        final ByteArrayOutputStream copyOut = new ByteArrayOutputStream();
        InputStream in = new BufferedInputStream(pipedIn, fileLength + 1);
        in.mark(fileLength + 1);
        bufferedStreams.put(fid, in);
        (new Thread(new Runnable() {
            public void run() {
                byte[] buf = new byte[BUFFER_SIZE];
                try {
                    while (true) {
                        int bytesRead = unBufferedIn.read(buf);
                        if (bytesRead < 0) {
                            break;
                        }
                        out.write(buf, 0, bytesRead);
                        copyOut.write(buf, 0, bytesRead);
                        dl.bytesRead += bytesRead;
                    }
                    out.flush();
                    out.close();
                    copyOut.flush();
                    byte[] copyOutBytes = copyOut.toByteArray();
                    filesBytes.put(fid, copyOutBytes);
                    copyOut.close();
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                    /* FIXME: what if something goes wrong inside this thread? */
                }
            }
        })).start();
    }

    /**
     * Puts/replaces the given file in this Passwd Manager. Triggers all
     * necessary changes (COM/SOD file update, resigning, etc.)
     * 
     * @param fid
     *            the FID of the file
     * @param bytes
     *            the file contents
     */
    public void putFile(short fid, byte[] bytes) {
        putFile(fid, bytes, false);
    }

    /**
     * Puts/replaces the given file in this card. Triggers all
     * necessary changes (COM/SOD file update, resigning, etc.)
     * 
     * @param fid
     *            the FID of the file
     * @param bytes
     *            the file contents
     * @param eacProtection
     *            whether the file should be EAC protected
     */
    public void putFile(short fid, byte[] bytes, boolean eacProtection) {
        if (bytes == null) {
            return;
        }
        updateCOMSODfiles = true;
        filesBytes.put(fid, bytes);
        eapFlags.put(fid, eacProtection);
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        int fileLength = bytes.length;
        in.mark(fileLength + 1);
        bufferedStreams.put(fid, in);
        fileLengths.put(fid, fileLength);
        // FIXME: is this necessary?
        totalLength += fileLength;
        if (fid != BasicService.EF_COM && fid != BasicService.EF_SOD) {
            updateCOMSODFile(null);
        }
    }

    /**
     * Removes the given file in this card. Triggers all necessary
     * changes (COM/SOD file update, resigning, etc.)
     * 
     * @param fid
     *            the FID of the file to be removed
     */
    public void removeFile(short fid) {
        filesBytes.remove(fid);
        eapFlags.remove(fid);
        int fileLength = fileLengths.get(fid);
        bufferedStreams.remove(fid);
        fileLengths.remove(fid);
        totalLength -= fileLength;
        if (fid != BasicService.EF_COM && fid != BasicService.EF_SOD) {
            updateCOMSODFile(null);
        }
    }

    private void updateCOMSODFile(X509Certificate newCertificate) {
        if (!updateCOMSODfiles || sodFile == null || comFile == null) {
            return;
        }
        try {
            String digestAlg = sodFile.getDigestAlgorithm();
            X509Certificate cert = newCertificate != null ? newCertificate : sodFile.getDocSigningCertificate();
            //String signatureAlg = sodFile.getDigestEncryptionAlgorithm();
            String signatureAlg = cert.getSigAlgName();

            byte[] signature = sodFile.getEncryptedDigest();
            Map<Integer, byte[]> dgHashes = new TreeMap<Integer, byte[]>();
            List<Short> dgFids = getFileList();
            if (dgFids.size() < 4) {
                // At least two proper data groups are needed to construct
                // a valid SOD
                return;
            }
            comFile.getTagList().clear();
            Collections.sort(dgFids);
            MessageDigest digest = MessageDigest.getInstance(digestAlg);
            for (Short fid : dgFids) {
                if (fid != BasicService.EF_COM && fid != BasicService.EF_SOD) {
                    byte[] data = getFileBytes(fid);
                    byte tag = data[0];
                    dgHashes.put(FileStructure.lookupDataGroupNumberByTag(tag), digest.digest(data));
                    comFile.insertTag(new Integer(tag));
                }
            }
            if (signer != null) {
                signer.setCertificate(cert);
                sodFile = new DG_SOD(digestAlg, signatureAlg, dgHashes, signer, cert);
            } else {
                sodFile = new DG_SOD(digestAlg, signatureAlg, dgHashes, signature, cert);
            }
            updateSOIS();
            putFile(BasicService.EF_SOD, sodFile.getEncoded());
            putFile(BasicService.EF_COM, comFile.getEncoded());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Gets the contents of the given file.
     * 
     * @param fid
     *            the file's FID
     * @return the file contents
     */
    public byte[] getFileBytes(short fid) {
        byte[] result = filesBytes.get(fid);
        if (result != null) {
            return result;
        }
        InputStream in = getInputStream(fid);
        if (in == null) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buf = new byte[256];
        while (true) {
            try {
                int bytesRead = in.read(buf);
                if (bytesRead < 0) {
                    break;
                }
                out.write(buf, 0, bytesRead);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
        return out.toByteArray();
    }

    /**
     * Sets the current document signer. Triggers all
     * necessary changes (COM/SOD file update, resigning, etc.)
     * 
     * @param signer
     *            the document signer
     */
    public void setSigner(DocumentSigner signer) {
        updateCOMSODfiles = true;
        this.signer = signer;
        updateCOMSODFile(null);
    }

    /**
     * Sets the current document signing certificate. Alters SOD file. Triggers
     * all necessary changes (COM/SOD file update, resigning, etc.)
     * 
     * @param newCertificate
     *            the new document signing certificate
     */
    public void setDocSigningCertificate(X509Certificate newCertificate) {
        updateCOMSODfiles = true;
        updateCOMSODFile(newCertificate);
    }

    // Helper: update the Security Object Indicators in the COM file
    private void updateSOIS() {
        if (!updateCOMSODfiles || comFile == null) {
            return;
        }
        SecurityObjectIndicatorDG15 soi15 = null;
        SecurityObjectIndicatorDG14 soi14 = null;
        if (getFileList().contains(BasicService.EF_DG15) && aaPrivateKey != null) {
            soi15 = new SecurityObjectIndicatorDG15(new ArrayList<Integer>());
        }
        if (getFileList().contains(BasicService.EF_DG14) && eacPrivateKey != null && cvcaCertificate != null) {
            List<Integer> dgs = new ArrayList<Integer>();
            for (short fid : getFileList()) {
                if (eapFlags.get(fid)) {
                    dgs.add(FileStructure.lookupDataGroupNumberByFID(fid));
                }
            }
            Collections.sort(dgs);
            soi14 = new SecurityObjectIndicatorDG14(cvcaCertificate, dgs);
        }
        int length = (soi15 != null ? 1 : 0) + (soi14 != null ? 1 : 0);
        SecurityObjectIndicator[] sois = new SecurityObjectIndicator[length];
        int index = 0;
        if (soi15 != null) {
            sois[index++] = soi15;
        }
        if (soi14 != null) {
            sois[index] = soi14;
        }
        comFile.setSOIArray(sois);
    }

    /**
     * Sets the current EAC CVCA certificate. Alters COM file. Triggers the
     * update of Security Object Indicators in the COM file.
     * 
     * @param cert
     *            the new EAC CVCA certificate
     */
    public void setCVCertificate(CVCertificate cert) {
        this.cvcaCertificate = cert;
        updateCOMSODfiles = true;
        updateSOIS();
    }

    /**
     * @return the stored EAC CVCA certificate
     */
    public CVCertificate getCVCertificate() {
        return cvcaCertificate;
    }

    /**
     * 
     * @return the current document signer
     */
    public DocumentSigner getSigner() {
        return signer;
    }

    /**
     * Sets the EAC key pair (alters DG14). Triggers all necessary changes
     * (COM/SOD file update, resigning, etc.)
     * 
     * @param keyPair
     *            the EAC key pair
     */
    public void setEACKeys(KeyPair keyPair) {
        this.eacPrivateKey = keyPair.getPrivate();
        Map<Integer, PublicKey> key = new TreeMap<Integer, PublicKey>();
        key.put(-1, keyPair.getPublic());
        DG_14_FILE dg14file = new DG_14_FILE(key);
        putFile(BasicService.EF_DG14, dg14file.getEncoded());
    }

    /**
     * Sets the AA key pair (alters DG15). Triggers all necessary changes
     * (COM/SOD file update, resigning, etc.)
     * 
     * @param keyPair
     *            the AA key pair
     */
    public void setAAKeys(KeyPair keyPair) {
        this.aaPrivateKey = keyPair.getPrivate();
        DG_15_FILE dg15file = new DG_15_FILE(keyPair.getPublic());
        putFile(BasicService.EF_DG15, dg15file.getEncoded());
    }

    /**
     * 
     * @return the current AA private key
     */
    public PrivateKey getAAPrivateKey() {
        return aaPrivateKey;
    }

    /**
     * Sets the current AA private key.
     * 
     * @param key
     *            the AA private key.
     */
    public void setAAPrivateKey(PrivateKey key) {
        aaPrivateKey = key;
        updateSOIS();
    }

    /**
     * Sets the AA public key (alters DG15). Triggers all necessary changes
     * (COM/SOD file update, resigning, etc.)
     * 
     * @param key
     *            the AA public key
     */
    public void setAAPublicKey(PublicKey key) {
        DG_15_FILE dg15file = new DG_15_FILE(key);
        putFile(BasicService.EF_DG15, dg15file.getEncoded());
    }

    /**
     * 
     * @return the current EAC private key
     */
    public PrivateKey getEACPrivateKey() {
        return eacPrivateKey;
    }

    /**
     * 
     * @return whether has EAC support
     */
    public boolean hasEAC() {
        return eacSupport;
    }

    /**
     * 
     * @return whether EAC was successfully performed.
     */
    public boolean wasEACPerformed() {
        return eacSuccess;
    }

    /**
     * 
     * @return the list of FIDs that are EAC protected on this card.
     */
    public List<Short> getEACFiles() {
        return eacFids;
    }

    /**
     * 
     * @return total length of all the files.
     */
    public int getTotalLength() {
        return totalLength;
    }

    /**
     * 
     * @return total number of files read in so far from the card
     *         (card).
     */
    public int getBytesRead() {
        return bytesRead;
    }

    /**
     * 
     * @return the list of all FIDS contained in this driving card.
     */
    public List<Short> getFileList() {
        List<Short> result = new ArrayList<Short>();
        result.addAll(fileLengths.keySet());
        return result;
    }

    /**
     * 
     * @return the stored key seed, null if missing
     */
    public byte[] getKeySeed() {
        return keySeed;
    }

    /**
     * Sets the key seed. 
     */
    public void setKeySeed(byte[] keySeed) {
        this.keySeed = keySeed;
    }

    /**
     * Uploads this passwdmanager to the card using the provided
     * Personalization service.
     * 
     * @param persoService
     *            the passwdmanager personalization service
     * @throws CardServiceException
     *             on errors
     */
    public void upload(PersoService persoService) throws CardServiceException {
        upload(persoService, null);
    }

    /**
     * Uploads this passwdmanager to the card using the provided
     * Personalization service.
     * 
     * @param persoService
     *            the passwdmanager personalization service
     * @param keySeed
     *            the key seed string (SAI) to use
     * @throws CardServiceException
     *             on errors
     */
    public void upload(PersoService persoService, byte[] keySeed) throws CardServiceException {
        if (keySeed == null && this.keySeed != null) {
            keySeed = this.keySeed;
            if (keySeed.length != 16) {
                throw new CardServiceException("Wrong key seed length.");
            }
        }

        List<Short> fileList = getFileList();
        String sicId = null;

        boolean enableAASupport = aaPrivateKey != null;
        if (fileList.contains(BasicService.EF_DG15) != enableAASupport) {
            throw new CardServiceException("DG15 present, but no AA private key found, or vice versa.");
        }

        boolean enableCASupport = eacPrivateKey != null;
        if (fileList.contains(BasicService.EF_DG14) != enableCASupport) {
            throw new CardServiceException("DG14 present, but no CA private key found, or vice versa.");
        }
        boolean enableTASupport = enableCASupport && cvcaCertificate != null;

        List<Integer> eacDGS = new ArrayList<Integer>();
        for (SecurityObjectIndicator soi : comFile.getSOIArray()) {
            if (soi instanceof SecurityObjectIndicatorDG15) {
                if (!enableAASupport) {
                    throw new CardServiceException("AA support declared in COM, but no required AA data present.");
                }
            } else if (soi instanceof SecurityObjectIndicatorDG14) {
                if (!enableTASupport) {
                    throw new CardServiceException(
                            "EAC support declared in COM, but no required CA/TA data present.");
                }
                eacDGS.addAll(((SecurityObjectIndicatorDG14) soi).getDataGroups());
            }
        }

        for (short fid : fileList) {
            byte[] fileBytes = getFileBytes(fid);
            boolean eacProtection = eacDGS.contains(FileStructure.lookupDataGroupNumberByFID(fid));
            persoService.createFile(fid, (short) fileBytes.length, eacProtection);
            persoService.selectFile(fid);
            ByteArrayInputStream in = new ByteArrayInputStream(fileBytes);
            persoService.writeFile(fid, in);
            if (enableTASupport && fid == BasicService.EF_DG1) {
                try {
                    DG_1_FILE dg1 = new DG_1_FILE(new ByteArrayInputStream(fileBytes));
                    sicId = dg1.getInfo().id;
                } catch (IOException ioe) {
                }
            }
        }
        if (enableAASupport) {
            persoService.putPrivateKey(aaPrivateKey);
        }
        if (enableCASupport) {
            persoService.putPrivateEACKey((ECPrivateKey) eacPrivateKey);
        }
        if (enableTASupport) {
            persoService.putCVCertificate(cvcaCertificate);
            persoService.setSicId(sicId);
        }
        if (keySeed != null) {
            persoService.setBAC(keySeed);
        }
        persoService.lockApplet();
    }

}