org.signserver.module.mrtdsodsigner.jmrtd.SODFile.java Source code

Java tutorial

Introduction

Here is the source code for org.signserver.module.mrtdsodsigner.jmrtd.SODFile.java

Source

/*
 * JMRTD - A Java API for accessing machine readable travel documents.
 *
 * Copyright (C) 2006 - 2010  The JMRTD team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * $Id: SODFile.java 3715 2013-08-17 20:17:15Z netmackan $
 */

package org.signserver.module.mrtdsodsigner.jmrtd;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.PSSParameterSpec;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.security.auth.x500.X500Principal;
import net.sourceforge.scuba.tlv.BERTLVInputStream;
import net.sourceforge.scuba.tlv.BERTLVObject;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.cms.SignedData;
import org.bouncycastle.asn1.cms.SignerIdentifier;
import org.bouncycastle.asn1.cms.SignerInfo;
import org.bouncycastle.asn1.icao.DataGroupHash;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX500NameUtil;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.signserver.module.mrtdsodsigner.bc.asn1.icao.LDSSecurityObject;
import org.signserver.module.mrtdsodsigner.bc.asn1.icao.LDSVersionInfo;

/**
 * File structure for the EF_SOD (Security Object Data) file.
 * This file contains the security object.
 * 
 * @author Wojciech Mostowski (woj@cs.ru.nl)
 * @author Martijn Oostdijk (martijn.oostdijk@gmail.com)
 * 
 * @version $Revision: 1270 $
 */
public class SODFile extends PassportFile {
    //   private static final DERObjectIdentifier SHA1_HASH_ALG_OID = new DERObjectIdentifier("1.3.14.3.2.26");
    //   private static final DERObjectIdentifier SHA1_WITH_RSA_ENC_OID = new DERObjectIdentifier("1.2.840.113549.1.1.5");
    //   private static final DERObjectIdentifier SHA256_HASH_ALG_OID = new DERObjectIdentifier("2.16.840.1.101.3.4.2.1");
    //   private static final DERObjectIdentifier E_CONTENT_TYPE_OID = new DERObjectIdentifier("1.2.528.1.1006.1.20.1");

    private static final ASN1ObjectIdentifier ICAO_SOD_OID = new ASN1ObjectIdentifier("2.23.136.1.1.1");
    private static final ASN1ObjectIdentifier SIGNED_DATA_OID = new ASN1ObjectIdentifier("1.2.840.113549.1.7.2");
    private static final ASN1ObjectIdentifier RFC_3369_CONTENT_TYPE_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.9.3");
    private static final ASN1ObjectIdentifier RFC_3369_MESSAGE_DIGEST_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.9.4");

    private static final ASN1ObjectIdentifier PKCS1_RSA_OID = new ASN1ObjectIdentifier("1.2.840.113549.1.1.1");
    private static final ASN1ObjectIdentifier PKCS1_MD2_WITH_RSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.1.2");
    private static final ASN1ObjectIdentifier PKCS1_MD4_WITH_RSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.1.3");
    private static final ASN1ObjectIdentifier PKCS1_MD5_WITH_RSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.1.4");
    private static final ASN1ObjectIdentifier PKCS1_SHA1_WITH_RSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.1.5");
    private static final ASN1ObjectIdentifier PKCS1_MGF1_OID = new ASN1ObjectIdentifier("1.2.840.113549.1.1.8");
    private static final ASN1ObjectIdentifier PKCS1_RSA_PSS_OID = new ASN1ObjectIdentifier("1.2.840.113549.1.1.10");
    private static final ASN1ObjectIdentifier PKCS1_SHA256_WITH_RSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.1.11");
    private static final ASN1ObjectIdentifier PKCS1_SHA384_WITH_RSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.1.12");
    private static final ASN1ObjectIdentifier PKCS1_SHA512_WITH_RSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.1.13");
    private static final ASN1ObjectIdentifier PKCS1_SHA224_WITH_RSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.113549.1.1.14");
    private static final ASN1ObjectIdentifier X9_SHA1_WITH_ECDSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.10045.4.1");
    private static final ASN1ObjectIdentifier X9_SHA224_WITH_ECDSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.10045.4.3.1");
    private static final ASN1ObjectIdentifier X9_SHA256_WITH_ECDSA_OID = new ASN1ObjectIdentifier(
            "1.2.840.10045.4.3.2");
    private static final ASN1ObjectIdentifier IEEE_P1363_SHA1_OID = new ASN1ObjectIdentifier("1.3.14.3.2.26");

    private static final HashMap<String, ASN1Encodable> algorithmParameters = new HashMap<String, ASN1Encodable>();

    private static final Provider PROVIDER = new org.bouncycastle.jce.provider.BouncyCastleProvider();

    private static final Logger LOGGER = Logger.getLogger("org.jmrtd");

    static {

        algorithmParameters.put("SHA1withRSAandMGF1",
                new RSASSAPSSparams(new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, new DERNull()),
                        new AlgorithmIdentifier(PKCS1_MGF1_OID,
                                new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, new DERNull())),
                        new ASN1Integer(20), new ASN1Integer(1)));

        algorithmParameters.put("SHA224withRSAandMGF1",
                new RSASSAPSSparams(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, new DERNull()),
                        new AlgorithmIdentifier(PKCS1_MGF1_OID,
                                new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, new DERNull())),
                        new ASN1Integer(28), new ASN1Integer(1)));

        algorithmParameters.put("SHA256withRSAandMGF1",
                new RSASSAPSSparams(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, new DERNull()),
                        new AlgorithmIdentifier(PKCS1_MGF1_OID,
                                new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, new DERNull())),
                        new ASN1Integer(32), new ASN1Integer(1)));

        algorithmParameters.put("SHA384withRSAandMGF1",
                new RSASSAPSSparams(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, new DERNull()),
                        new AlgorithmIdentifier(PKCS1_MGF1_OID,
                                new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, new DERNull())),
                        new ASN1Integer(48), new ASN1Integer(1)));

        algorithmParameters.put("SHA512withRSAandMGF1",
                new RSASSAPSSparams(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512, new DERNull()),
                        new AlgorithmIdentifier(PKCS1_MGF1_OID,
                                new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512, new DERNull())),
                        new ASN1Integer(64), new ASN1Integer(1)));
    }

    private SignedData signedData;

    /**
     * Constructs a Security Object data structure.
     *
     * @param digestAlgorithm a digest algorithm, such as "SHA1" or "SHA256"
     * @param digestEncryptionAlgorithm a digest encryption algorithm, such as "SHA256withRSA"
     * @param dataGroupHashes maps datagroupnumbers (1 to 16) to hashes of the data groups
     * @param encryptedDigest the signature (the encrypted digest) over the hashes.
     * @param docSigningCertificate the document signing certificate
     * 
     * @throws NoSuchAlgorithmException if either of the algorithm parameters is not recognized
     * @throws CertificateException if the document signing certificate cannot be used
     * @deprecated Usage of this constructor is unclear. Also currently it will not work with RSASSA-PSS signatures.
     */
    @Deprecated
    public SODFile(String digestAlgorithm, String digestEncryptionAlgorithm, Map<Integer, byte[]> dataGroupHashes,
            byte[] encryptedDigest, X509Certificate docSigningCertificate)
            throws NoSuchAlgorithmException, CertificateException, IOException {
        signedData = createSignedData(digestAlgorithm, digestEncryptionAlgorithm, dataGroupHashes, encryptedDigest,
                docSigningCertificate);
    }

    /**
     * Constructs a Security Object data structure using a specified signature provider.
     *
     * @param digestAlgorithm a digest algorithm, such as "SHA1" or "SHA256"
     * @param digestEncryptionAlgorithm a digest encryption algorithm, such as "SHA256withRSA"
     * @param dataGroupHashes maps datagroup numbers (1 to 16) to hashes of the data groups
     * @param privateKey private key to sign the data
     * @param docSigningCertificate the document signing certificate
     * @param provider specific signature provider that should be used to create the signature 
     * 
     * @throws NoSuchAlgorithmException if either of the algorithm parameters is not recognized
     * @throws CertificateException if the document signing certificate cannot be used
     */
    public SODFile(String digestAlgorithm, String digestEncryptionAlgorithm, Map<Integer, byte[]> dataGroupHashes,
            PrivateKey privateKey, X509Certificate docSigningCertificate, String provider)
            throws NoSuchAlgorithmException, CertificateException, IOException {
        signedData = createSignedData(digestAlgorithm, digestEncryptionAlgorithm, dataGroupHashes, privateKey,
                docSigningCertificate, provider);
    }

    /**
     * Constructs a Security Object data structure using a specified signature provider.
     *
     * @param digestAlgorithm a digest algorithm, such as "SHA1" or "SHA256"
     * @param digestEncryptionAlgorithm a digest encryption algorithm, such as "SHA256withRSA"
     * @param dataGroupHashes maps datagroup numbers (1 to 16) to hashes of the data groups
     * @param privateKey private key to sign the data
     * @param docSigningCertificate the document signing certificate
     * @param provider specific signature provider that should be used to create the signature
     * @param ldsVersion version of the Logical Data Structure (LDS) in the format "aabb". (Example: 1.8 becomes "0108".) Before LDS version 1.8 this should be null.
     * @param unicodeVersion version of the Unicode Standard in the format "aabbcc". (Example: 4.0.0 becomes "040000".) Before LDS version 1.8 this should be null.
     *
     * @throws NoSuchAlgorithmException if either of the algorithm parameters is not recognized
     * @throws CertificateException if the document signing certificate cannot be used
     */
    public SODFile(String digestAlgorithm, String digestEncryptionAlgorithm, Map<Integer, byte[]> dataGroupHashes,
            PrivateKey privateKey, X509Certificate docSigningCertificate, String provider, String ldsVersion,
            String unicodeVersion) throws NoSuchAlgorithmException, CertificateException, IOException {
        signedData = createSignedData(digestAlgorithm, digestEncryptionAlgorithm, dataGroupHashes, privateKey,
                docSigningCertificate, provider, ldsVersion, unicodeVersion);
    }

    /**
     * Constructs a Security Object data structure.
     *
     * @param digestAlgorithm a digest algorithm, such as "SHA1" or "SHA256"
     * @param digestEncryptionAlgorithm a digest encryption algorithm, such as "SHA256withRSA"
     * @param dataGroupHashes maps datagroup numbers (1 to 16) to hashes of the data groups
     * @param privateKey private key to sign the data
     * @param docSigningCertificate the document signing certificate
     * 
     * @throws NoSuchAlgorithmException if either of the algorithm parameters is not recognized
     * @throws CertificateException if the document signing certificate cannot be used
     */
    public SODFile(String digestAlgorithm, String digestEncryptionAlgorithm, Map<Integer, byte[]> dataGroupHashes,
            PrivateKey privateKey, X509Certificate docSigningCertificate)
            throws NoSuchAlgorithmException, CertificateException, IOException {
        signedData = createSignedData(digestAlgorithm, digestEncryptionAlgorithm, dataGroupHashes, privateKey,
                docSigningCertificate, null);
    }

    /**
     * Constructs a Security Object data structure.
     *
     * @param in some inputstream
     * @throws IOException if something goes wrong
     */
    public SODFile(InputStream in) throws IOException {
        BERTLVInputStream tlvIn = new BERTLVInputStream(in);
        tlvIn.readTag();
        tlvIn.readLength();
        ASN1InputStream asn1in = new ASN1InputStream(in);
        ASN1Sequence seq = (ASN1Sequence) asn1in.readObject();
        /* DERObjectIdentifier objectIdentifier = (DERObjectIdentifier) seq.getObjectAt(0); */ /* FIXME: do we need this? */
        //DERTaggedObject o = (DERTaggedObject)seq.getObjectAt(1);
        /* TODO: where is this tagNo specified? */
        // int tagNo =  o.getTagNo();
        ASN1Sequence s2 = (ASN1Sequence) ((DERTaggedObject) seq.getObjectAt(1)).getObject();

        this.signedData = SignedData.getInstance(s2);
    }

    /**
     * The tag of this file.
     * 
     * @return the tag
     */
    public int getTag() {
        return EF_SOD_TAG;
    }

    public byte[] getEncoded() throws IOException {
        if (isSourceConsistent) {
            return sourceObject;
        }

        /* TODO: where is that DERTaggedObject specified? */
        ASN1Encodable[] fileContents = { SIGNED_DATA_OID, new DERTaggedObject(0, signedData) };
        ASN1Sequence fileContentsObject = new DERSequence(fileContents);
        BERTLVObject sodFile = new BERTLVObject(EF_SOD_TAG, fileContentsObject.getEncoded(), false);
        return sodFile.getEncoded();
    }

    /**
     * Gets the stored data group hashes.
     *
     * @return data group hashes indexed by data group numbers (1 to 16)
     */
    public Map<Integer, byte[]> getDataGroupHashes() {
        DataGroupHash[] hashObjects = getSecurityObject(signedData).getDatagroupHash();
        Map<Integer, byte[]> hashMap = new HashMap<Integer, byte[]>(); /* HashMap... get it? :D */
        for (int i = 0; i < hashObjects.length; i++) {
            DataGroupHash hashObject = hashObjects[i];
            int number = hashObject.getDataGroupNumber();
            byte[] hashValue = hashObject.getDataGroupHashValue().getOctets();
            hashMap.put(number, hashValue);
        }
        return hashMap;
    }

    /**
     * Gets the signature (the encrypted digest) over the hashes.
     *
     * @return the encrypted digest
     */
    public byte[] getEncryptedDigest() {
        return getEncryptedDigest(signedData);
    }

    /**
     * Gets the name of the algorithm used in the data group hashes.
     * 
     * @return an algorithm string such as "SHA1" or "SHA256"
     */
    public String getDigestAlgorithm() {
        try {
            return lookupMnemonicByOID(getSecurityObject(signedData).getDigestAlgorithmIdentifier().getAlgorithm());
        } catch (NoSuchAlgorithmException nsae) {
            nsae.printStackTrace();
            throw new IllegalStateException(nsae.toString());
        }
    }

    /**
     * Gets the name of the algorithm used in the signature.
     * 
     * @return an algorithm string such as "SHA256withRSA"
     */
    public String getDigestEncryptionAlgorithm() {
        try {
            final DERObjectIdentifier algorithm = getSignerInfo(signedData).getDigestEncryptionAlgorithm()
                    .getAlgorithm();
            String result = lookupMnemonicByOID(algorithm);
            if (PKCS1_RSA_PSS_OID.toString().equals(algorithm.toString())) {
                try {
                    final ASN1Encodable parameters = getSignerInfo(signedData).getDigestEncryptionAlgorithm()
                            .getParameters();
                    if (parameters != null) {
                        AlgorithmParameters params = AlgorithmParameters.getInstance("PSS");
                        params.init(parameters.toASN1Primitive().getEncoded());
                        final PSSParameterSpec spec = params.getParameterSpec(PSSParameterSpec.class);
                        result = lookupMnemonicByOID(new DERObjectIdentifier(spec.getDigestAlgorithm()))
                                + "withRSAand"
                                + lookupMnemonicByOID(new DERObjectIdentifier(spec.getMGFAlgorithm()));
                    }
                } catch (InvalidParameterSpecException ignored) {
                } catch (IOException ignored) {
                }
            }
            return result;
        } catch (NoSuchAlgorithmException nsae) {
            nsae.printStackTrace();
            throw new IllegalStateException(nsae.toString());
        }
    }

    /**
    * Gets the version of the LDS if stored in the Security Object (SOd).
    *
    * @return the version of the LDS in "aabb" format or null if LDS &lt; V1.8
     * @since LDS V1.8
    */
    public String getLdsVersion() {
        LDSVersionInfo ldsVersionInfo = getSecurityObject(signedData).getLdsVersionInfo();
        if (ldsVersionInfo == null) {
            return null;
        } else {
            return ldsVersionInfo.getLdsVersion();
        }
    }

    /**
    * Gets the version of unicode if stored in the Security Object (SOd).
    *
    * @return the unicode version in "aabbcc" format or null if LDS &lt; V1.8
     * @since LDS V1.8
    */
    public String getUnicodeVersion() {
        LDSVersionInfo ldsVersionInfo = getSecurityObject(signedData).getLdsVersionInfo();
        if (ldsVersionInfo == null) {
            return null;
        } else {
            return ldsVersionInfo.getUnicodeVersion();
        }
    }

    /**
     * Gets the document signing certificate.
     * Use this certificate to verify that
     * <i>eSignature</i> is a valid signature for
     * <i>eContent</i>. This certificate itself is
     * signed using the country signing certificate.
      * 
     * @return the document signing certificate
     */
    public X509Certificate getDocSigningCertificate() throws IOException, CertificateException {
        ASN1Set certs = signedData.getCertificates();
        if (certs == null || certs.size() <= 0) {
            return null;
        }
        if (certs.size() != 1) {
            LOGGER.warning("Found " + certs.size() + " certificates");
        }
        final X509CertificateHolder cert = new X509CertificateHolder(
                certs.getObjectAt(0).toASN1Primitive().getEncoded());
        return new JcaX509CertificateConverter().setProvider("BC").getCertificate(cert);
    }

    /**
     * Verifies the signature over the contents of the security object.
     * Clients can also use the accessors of this class and check the
     * validity of the signature for themselves.
     * 
     * See RFC 3369, Cryptographic Message Syntax, August 2002,
     * Section 5.4 for details.
     * 
     * @param docSigningCert the certificate to use
     *        (should be X509 certificate)
     * 
     * @return status of the verification
     * 
     * @throws GeneralSecurityException if something goes wrong
     */
    public boolean checkDocSignature(Certificate docSigningCert) throws GeneralSecurityException {
        byte[] eContent = null;
        byte[] signature = getEncryptedDigest(signedData);

        try {
            eContent = getEContent(signedData);
        } catch (IOException ioe) {
            throw new GeneralSecurityException("Unable to get the contents of the security object", ioe);
        }

        DERObjectIdentifier encAlgId = getSignerInfo(signedData).getDigestEncryptionAlgorithm().getAlgorithm();
        String encAlgJavaString = lookupMnemonicByOID(encAlgId);

        // For the cases where the signature is simply a digest (haven't seen a passport like this, 
        // thus this is guessing)

        if (encAlgId.getId() == null) {
            String digestAlg = getSignerInfo(signedData).getDigestAlgorithm().getAlgorithm().getId();
            MessageDigest digest = null;
            try {
                digest = MessageDigest.getInstance(digestAlg);
            } catch (Exception e) {
                /* FIXME: Warn client that they should perhaps add BC as provider? */
                digest = MessageDigest.getInstance(digestAlg, PROVIDER);
            }
            digest.update(eContent);
            byte[] digestBytes = digest.digest();
            return Arrays.equals(digestBytes, signature);
        }

        if (encAlgId.equals(PKCS1_RSA_OID)) {
            encAlgJavaString = lookupMnemonicByOID(getSignerInfo(signedData).getDigestAlgorithm().getAlgorithm())
                    + "withRSA";
        }

        Signature sig = null;
        try {
            sig = Signature.getInstance(encAlgJavaString);
        } catch (Exception e) {
            /* FIXME: Warn client that they should perhaps add BC as provider? */
            sig = Signature.getInstance(encAlgJavaString, PROVIDER);
        }
        if (encAlgId.equals(PKCS1_RSA_PSS_OID)) {
            try {
                final ASN1Encodable parameters = getSignerInfo(signedData).getDigestEncryptionAlgorithm()
                        .getParameters();
                if (parameters != null) {
                    AlgorithmParameters params = AlgorithmParameters.getInstance("PSS");
                    params.init(parameters.toASN1Primitive().getEncoded());
                    sig.setParameter(params.getParameterSpec(PSSParameterSpec.class));
                }
            } catch (IOException ex) {
                throw new GeneralSecurityException("Unable to parse algorithm parameters", ex);
            }
        }
        sig.initVerify(docSigningCert);
        sig.update(eContent);
        return sig.verify(signature);

        // 2. Do it manually, decrypt the signature and extract the hashing algorithm
        /*
        try {
           Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
           c.init(Cipher.DECRYPT_MODE, docSigningCert);
           c.update(signature);
           byte[] decryptedBytes = c.doFinal();
           String id = getHashId(decryptedBytes);
           byte[] expectedHash = getHashBytes(decryptedBytes);
           MessageDigest digest = MessageDigest.getInstance(id);
           digest.update(eContent);
           byte[] digestBytes = digest.digest();
           result = Arrays.equals(digestBytes, expectedHash);
        }catch(Exception e) {
            
        }
          String[] sigAlgs = new String[] {"SHA1withRSA", "SHA1withRSA/PSS", "SHA256withRSA", "SHA256withRSA/PSS"};
         */
    }

    public X500Principal getIssuerX500Principal() throws IOException {
        IssuerAndSerialNumber issuerAndSerialNumber = getIssuerAndSerialNumber();
        X500Name name = issuerAndSerialNumber.getName();

        return new X500Principal(name.getEncoded(ASN1Encoding.DER));
    }

    public BigInteger getSerialNumber() {
        IssuerAndSerialNumber issuerAndSerialNumber = getIssuerAndSerialNumber();
        BigInteger serialNumber = issuerAndSerialNumber.getSerialNumber().getValue();
        return serialNumber;
    }

    /**
     * Gets a textual representation of this file.
     * 
     * @return a textual representation of this file
     */
    @Override
    public String toString() {
        try {
            X509Certificate cert = getDocSigningCertificate();
            return "SODFile " + cert.getIssuerX500Principal();
        } catch (Exception e) {
            return "SODFile";
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (!obj.getClass().equals(this.getClass())) {
            return false;
        }
        SODFile other = (SODFile) obj;
        try {
            return Arrays.equals(getEncoded(), other.getEncoded());
        } catch (IOException e) {
            // shouldn't really happen...
            return false;
        }
    }

    @Override
    public int hashCode() {
        int hash = 0;

        try {
            hash += Arrays.hashCode(getEncoded());
        } catch (IOException e) {
            // NOPMD
        }

        return 11 * hash + 111;
    }

    /* ONLY PRIVATE METHODS BELOW */

    private static SignerInfo getSignerInfo(SignedData signedData) {
        ASN1Set signerInfos = signedData.getSignerInfos();
        if (signerInfos.size() > 1) {
            LOGGER.warning("Found " + signerInfos.size() + " signerInfos");
        }
        for (int i = 0; i < signerInfos.size(); i++) {
            SignerInfo info = new SignerInfo((DERSequence) signerInfos.getObjectAt(i));
            return info;
        }
        return null;
    }

    /**
     * Reads the security object (containing the hashes
     * of the data groups) found in the SOd on the passport.
     * 
     * @return the security object
     * 
     * @throws IOException
     */
    private static LDSSecurityObject getSecurityObject(SignedData signedData) {
        try {
            ContentInfo contentInfo = signedData.getEncapContentInfo();
            byte[] content = ((DEROctetString) contentInfo.getContent()).getOctets();
            ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(content));

            LDSSecurityObject sod = new LDSSecurityObject((ASN1Sequence) in.readObject());
            Object nextObject = in.readObject();

            if (nextObject != null) {
                LOGGER.warning("extra object found after LDSSecurityObject...");
            }
            return sod;
        } catch (IOException ioe) {
            throw new IllegalStateException("Could not read security object in signedData");
        }
    }

    /**
     * Gets the contents of the security object over which the
     * signature is to be computed. 
     * 
     * See RFC 3369, Cryptographic Message Syntax, August 2002,
     * Section 5.4 for details.
     * 
     * FIXME: Maybe throw an exception instead of issuing warnings
     * on stderr if signed attributes don't check out.
     *
     * @see #getDocSigningCertificate()
     * @see #getSignature()
     * 
     * @return the contents of the security object over which the
     *         signature is to be computed
     */
    private static byte[] getEContent(SignedData signedData) throws IOException {
        SignerInfo signerInfo = getSignerInfo(signedData);
        ASN1Set signedAttributesSet = signerInfo.getAuthenticatedAttributes();

        ContentInfo contentInfo = signedData.getEncapContentInfo();
        byte[] contentBytes = ((DEROctetString) contentInfo.getContent()).getOctets();

        if (signedAttributesSet.size() == 0) {
            /* Signed attributes absent, return content to be signed... */
            return contentBytes;
        } else {
            /* Signed attributes present (i.e. a structure containing a hash of the content), return that structure to be signed... */
            /* This option is taken by ICAO passports. */
            byte[] attributesBytes = signedAttributesSet.getEncoded();
            String digAlg = signerInfo.getDigestAlgorithm().getAlgorithm().getId();
            try {
                /* We'd better check that the content actually digests to the hash value contained! ;) */
                Enumeration<?> attributes = signedAttributesSet.getObjects();
                byte[] storedDigestedContent = null;
                while (attributes.hasMoreElements()) {
                    Attribute attribute = Attribute.getInstance(attributes.nextElement());
                    DERObjectIdentifier attrType = attribute.getAttrType();
                    if (attrType.equals(RFC_3369_MESSAGE_DIGEST_OID)) {
                        ASN1Set attrValuesSet = attribute.getAttrValues();
                        if (attrValuesSet.size() != 1) {
                            LOGGER.warning(
                                    "Expected only one attribute value in signedAttribute message digest in eContent!");
                        }
                        storedDigestedContent = ((DEROctetString) attrValuesSet.getObjectAt(0)).getOctets();
                    }
                }
                if (storedDigestedContent == null) {
                    LOGGER.warning("Error extracting signedAttribute message digest in eContent!");
                }
                MessageDigest dig = MessageDigest.getInstance(digAlg);
                byte[] computedDigestedContent = dig.digest(contentBytes);
                if (!Arrays.equals(storedDigestedContent, computedDigestedContent)) {
                    LOGGER.warning("Error checking signedAttribute message digest in eContent!");
                }
            } catch (NoSuchAlgorithmException nsae) {
                LOGGER.warning("Error checking signedAttribute in eContent! No such algorithm " + digAlg);
            }
            return attributesBytes;
        }
    }

    private IssuerAndSerialNumber getIssuerAndSerialNumber() {
        SignerInfo signerInfo = getSignerInfo(signedData);
        SignerIdentifier signerIdentifier = signerInfo.getSID();
        DERSequence idSeq = (DERSequence) signerIdentifier.getId();
        IssuerAndSerialNumber issuerAndSerialNumber = IssuerAndSerialNumber.getInstance(idSeq);
        return issuerAndSerialNumber;
    }

    /**
     * Gets the stored signature of the security object.
     * 
     * @see #getDocSigningCertificate()
     * 
     * @return the signature
     */
    private static byte[] getEncryptedDigest(SignedData signedData) {
        SignerInfo signerInfo = getSignerInfo(signedData);
        return signerInfo.getEncryptedDigest().getOctets();
    }

    /* METHODS BELOW ARE FOR CONSTRUCTING SOD STRUCTS */

    private static SignedData createSignedData(String digestAlgorithm, String digestEncryptionAlgorithm,
            Map<Integer, byte[]> dataGroupHashes, byte[] encryptedDigest, X509Certificate docSigningCertificate)
            throws NoSuchAlgorithmException, CertificateException, IOException {
        ASN1Set digestAlgorithmsSet = createSingletonSet(createDigestAlgorithms(digestAlgorithm));
        ContentInfo contentInfo = createContentInfo(digestAlgorithm, dataGroupHashes);
        byte[] content = ((DEROctetString) contentInfo.getContent()).getOctets();
        ASN1Set certificates = createSingletonSet(createCertificate(docSigningCertificate));
        ASN1Set crls = null;
        ASN1Set signerInfos = createSingletonSet(createSignerInfo(digestAlgorithm, digestEncryptionAlgorithm, null,
                content, encryptedDigest, docSigningCertificate).toASN1Primitive());
        return new SignedData(digestAlgorithmsSet, contentInfo, certificates, crls, signerInfos);
    }

    private static SignedData createSignedData(String digestAlgorithm, String digestEncryptionAlgorithm,
            Map<Integer, byte[]> dataGroupHashes, PrivateKey privateKey, X509Certificate docSigningCertificate,
            String provider) throws NoSuchAlgorithmException, CertificateException, IOException {
        return createSignedData(digestAlgorithm, digestEncryptionAlgorithm, dataGroupHashes, privateKey,
                docSigningCertificate, provider, null, null);
    }

    private static SignedData createSignedData(String digestAlgorithm, String digestEncryptionAlgorithm,
            Map<Integer, byte[]> dataGroupHashes, PrivateKey privateKey, X509Certificate docSigningCertificate,
            String provider, String ldsVersion, String unicodeVersion)
            throws NoSuchAlgorithmException, CertificateException, IOException {
        ASN1Set digestAlgorithmsSet = createSingletonSet(createDigestAlgorithms(digestAlgorithm));
        ContentInfo contentInfo = createContentInfo(digestAlgorithm, dataGroupHashes, ldsVersion, unicodeVersion);
        byte[] content = ((DEROctetString) contentInfo.getContent()).getOctets();

        ASN1Encodable digestEncryptionAlgorithmParams;
        byte[] encryptedDigest = null;
        try {
            byte[] dataToBeSigned = createAuthenticatedAttributes(digestAlgorithm, content)
                    .getEncoded(ASN1Encoding.DER);
            Signature s;
            if (provider != null) {
                s = Signature.getInstance(digestEncryptionAlgorithm, provider);
            } else {
                s = Signature.getInstance(digestEncryptionAlgorithm);
            }
            s.initSign(privateKey);
            s.update(dataToBeSigned);
            encryptedDigest = s.sign();
            if (PKCS1_RSA_PSS_OID.toString().equals(lookupOIDByMnemonic(digestEncryptionAlgorithm).toString())) {
                //                try {
                //                    digestEncryptionAlgorithmParams = ASN1Object.fromByteArray(
                //                            s.getParameters().getEncoded());
                //                } catch (UnsupportedOperationException ex) {
                //                    // Some providers does not support getting the parameters
                //                    // (i.e. SunPKCS11 provider). Instead we assume they
                //                    // use the default parameters.
                digestEncryptionAlgorithmParams = algorithmParameters.get(digestEncryptionAlgorithm);
                //                }
            } else {
                digestEncryptionAlgorithmParams = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        ASN1Set certificates = createSingletonSet(createCertificate(docSigningCertificate));
        ASN1Set crls = null;
        ASN1Set signerInfos = createSingletonSet(
                createSignerInfo(digestAlgorithm, digestEncryptionAlgorithm, digestEncryptionAlgorithmParams,
                        content, encryptedDigest, docSigningCertificate).toASN1Primitive());
        return new SignedData(digestAlgorithmsSet, contentInfo, certificates, crls, signerInfos);
    }

    private static ASN1Sequence createDigestAlgorithms(String digestAlgorithm) throws NoSuchAlgorithmException {
        DERObjectIdentifier algorithmIdentifier = lookupOIDByMnemonic(digestAlgorithm);
        ASN1Encodable[] result = { algorithmIdentifier };
        return new DERSequence(result);
    }

    private static ASN1Sequence createCertificate(X509Certificate cert) throws CertificateException {
        try {
            byte[] certSpec = cert.getEncoded();
            ASN1InputStream asn1In = new ASN1InputStream(certSpec);
            try {
                ASN1Sequence certSeq = (ASN1Sequence) (asn1In).readObject();
                return certSeq;
            } finally {
                asn1In.close();
            }
        } catch (IOException ioe) {
            throw new CertificateException("Could not construct certificate byte stream");
        }
    }

    private static ContentInfo createContentInfo(String digestAlgorithm, Map<Integer, byte[]> dataGroupHashes)
            throws NoSuchAlgorithmException, IOException {
        return createContentInfo(digestAlgorithm, dataGroupHashes, null, null);
    }

    private static ContentInfo createContentInfo(String digestAlgorithm, Map<Integer, byte[]> dataGroupHashes,
            String ldsVersion, String unicodeVersion) throws NoSuchAlgorithmException, IOException {
        DataGroupHash[] dataGroupHashesArray = new DataGroupHash[dataGroupHashes.size()];
        int i = 0;
        for (int dataGroupNumber : dataGroupHashes.keySet()) {
            byte[] hashBytes = dataGroupHashes.get(dataGroupNumber);
            DataGroupHash hash = new DataGroupHash(dataGroupNumber, new DEROctetString(hashBytes));
            dataGroupHashesArray[i++] = hash;
        }
        AlgorithmIdentifier digestAlgorithmIdentifier = new AlgorithmIdentifier(
                lookupOIDByMnemonic(digestAlgorithm));
        LDSVersionInfo ldsVersionInfo;
        if (ldsVersion == null) {
            ldsVersionInfo = null;
        } else {
            ldsVersionInfo = new LDSVersionInfo(new DERPrintableString(ldsVersion, true),
                    new DERPrintableString(unicodeVersion, true));
        }
        LDSSecurityObject sObject2 = new LDSSecurityObject(digestAlgorithmIdentifier, dataGroupHashesArray,
                ldsVersionInfo);
        return new ContentInfo(ICAO_SOD_OID, new DEROctetString(sObject2));
    }

    private static SignerInfo createSignerInfo(String digestAlgorithm, String digestEncryptionAlgorithm,
            ASN1Encodable digestEncryptionAlgorithmParams, byte[] content, byte[] encryptedDigest,
            X509Certificate docSigningCertificate) throws NoSuchAlgorithmException, CertificateEncodingException {
        /* Get the issuer name (CN, O, OU, C) from the cert and put it in a SignerIdentifier struct. */
        BigInteger serial = ((X509Certificate) docSigningCertificate).getSerialNumber();
        IssuerAndSerialNumber iasn = new IssuerAndSerialNumber(JcaX500NameUtil.getIssuer(docSigningCertificate),
                serial);
        SignerIdentifier sid = new SignerIdentifier(iasn);
        AlgorithmIdentifier digestAlgorithmObject = new AlgorithmIdentifier(lookupOIDByMnemonic(digestAlgorithm));
        final AlgorithmIdentifier digestEncryptionAlgorithmObject;
        if (digestEncryptionAlgorithmParams == null) {
            digestEncryptionAlgorithmObject = new AlgorithmIdentifier(
                    lookupOIDByMnemonic(digestEncryptionAlgorithm));
        } else {
            digestEncryptionAlgorithmObject = new AlgorithmIdentifier(
                    lookupOIDByMnemonic(digestEncryptionAlgorithm), digestEncryptionAlgorithmParams);
        }

        ASN1Set authenticatedAttributes = createAuthenticatedAttributes(digestAlgorithm, content); // struct containing the hash of content
        ASN1OctetString encryptedDigestObject = new DEROctetString(encryptedDigest); // this is the signature
        ASN1Set unAuthenticatedAttributes = null; // should be empty set?
        return new SignerInfo(sid, digestAlgorithmObject, authenticatedAttributes, digestEncryptionAlgorithmObject,
                encryptedDigestObject, unAuthenticatedAttributes);
    }

    private static ASN1Set createAuthenticatedAttributes(String digestAlgorithm, byte[] contentBytes)
            throws NoSuchAlgorithmException {
        MessageDigest dig = MessageDigest.getInstance(digestAlgorithm);
        byte[] digestedContentBytes = dig.digest(contentBytes);
        ASN1OctetString digestedContent = new DEROctetString(digestedContentBytes);
        Attribute contentTypeAttribute = new Attribute(RFC_3369_CONTENT_TYPE_OID, createSingletonSet(ICAO_SOD_OID));
        Attribute messageDigestAttribute = new Attribute(RFC_3369_MESSAGE_DIGEST_OID,
                createSingletonSet(digestedContent));
        ASN1Encodable[] result = { contentTypeAttribute.toASN1Primitive(),
                messageDigestAttribute.toASN1Primitive() };
        return new DERSet(result);
    }

    private static ASN1Set createSingletonSet(ASN1Encodable e) {
        ASN1Encodable[] result = { e };
        return new DERSet(result);
    }

    /**
     * Gets the common mnemonic string (such as "SHA1", "SHA256withRSA") given an OID.
     *
     * @param oid a BC OID
     *
     * @throws NoSuchAlgorithmException if the provided OID is not yet supported
     */
    private static String lookupMnemonicByOID(DERObjectIdentifier oid) throws NoSuchAlgorithmException {
        if (oid.equals(X509ObjectIdentifiers.organization)) {
            return "O";
        }
        if (oid.equals(X509ObjectIdentifiers.organizationalUnitName)) {
            return "OU";
        }
        if (oid.equals(X509ObjectIdentifiers.commonName)) {
            return "CN";
        }
        if (oid.equals(X509ObjectIdentifiers.countryName)) {
            return "C";
        }
        if (oid.equals(X509ObjectIdentifiers.stateOrProvinceName)) {
            return "ST";
        }
        if (oid.equals(X509ObjectIdentifiers.localityName)) {
            return "L";
        }
        if (oid.equals(X509ObjectIdentifiers.id_SHA1)) {
            return "SHA1";
        }
        if (oid.equals(NISTObjectIdentifiers.id_sha224)) {
            return "SHA224";
        }
        if (oid.equals(NISTObjectIdentifiers.id_sha256)) {
            return "SHA256";
        }
        if (oid.equals(NISTObjectIdentifiers.id_sha384)) {
            return "SHA384";
        }
        if (oid.equals(NISTObjectIdentifiers.id_sha512)) {
            return "SHA512";
        }
        if (oid.equals(X9_SHA1_WITH_ECDSA_OID)) {
            return "SHA1withECDSA";
        }
        if (oid.equals(X9_SHA224_WITH_ECDSA_OID)) {
            return "SHA224withECDSA";
        }
        if (oid.equals(X9_SHA256_WITH_ECDSA_OID)) {
            return "SHA256withECDSA";
        }
        if (oid.equals(PKCS1_MGF1_OID)) {
            return "MGF1";
        }
        if (oid.equals(PKCS1_RSA_OID)) {
            return "RSA";
        }
        if (oid.equals(PKCS1_MD2_WITH_RSA_OID)) {
            return "MD2withRSA";
        }
        if (oid.equals(PKCS1_MD4_WITH_RSA_OID)) {
            return "MD4withRSA";
        }
        if (oid.equals(PKCS1_MD5_WITH_RSA_OID)) {
            return "MD5withRSA";
        }
        if (oid.equals(PKCS1_SHA1_WITH_RSA_OID)) {
            return "SHA1withRSA";
        }
        if (oid.equals(PKCS1_SHA256_WITH_RSA_OID)) {
            return "SHA256withRSA";
        }
        if (oid.equals(PKCS1_SHA384_WITH_RSA_OID)) {
            return "SHA384withRSA";
        }
        if (oid.equals(PKCS1_SHA512_WITH_RSA_OID)) {
            return "SHA512withRSA";
        }
        if (oid.equals(PKCS1_SHA224_WITH_RSA_OID)) {
            return "SHA224withRSA";
        }
        if (oid.equals(IEEE_P1363_SHA1_OID)) {
            return "SHA1";
        }
        if (oid.equals(PKCS1_RSA_PSS_OID)) {
            return "RSASSA-PSS";
        }
        throw new NoSuchAlgorithmException("Unknown OID " + oid);
    }

    private static ASN1ObjectIdentifier lookupOIDByMnemonic(String name) throws NoSuchAlgorithmException {
        if (name.equals("O")) {
            return X509ObjectIdentifiers.organization;
        }
        if (name.equals("OU")) {
            return X509ObjectIdentifiers.organizationalUnitName;
        }
        if (name.equals("CN")) {
            return X509ObjectIdentifiers.commonName;
        }
        if (name.equals("C")) {
            return X509ObjectIdentifiers.countryName;
        }
        if (name.equals("ST")) {
            return X509ObjectIdentifiers.stateOrProvinceName;
        }
        if (name.equals("L")) {
            return X509ObjectIdentifiers.localityName;
        }
        if (name.equalsIgnoreCase("SHA1")) {
            return X509ObjectIdentifiers.id_SHA1;
        }
        if (name.equalsIgnoreCase("SHA224")) {
            return NISTObjectIdentifiers.id_sha224;
        }
        if (name.equalsIgnoreCase("SHA256")) {
            return NISTObjectIdentifiers.id_sha256;
        }
        if (name.equalsIgnoreCase("SHA384")) {
            return NISTObjectIdentifiers.id_sha384;
        }
        if (name.equalsIgnoreCase("SHA512")) {
            return NISTObjectIdentifiers.id_sha512;
        }
        if (name.equalsIgnoreCase("RSA")) {
            return PKCS1_RSA_OID;
        }
        if (name.equalsIgnoreCase("MD2withRSA")) {
            return PKCS1_MD2_WITH_RSA_OID;
        }
        if (name.equalsIgnoreCase("MD4withRSA")) {
            return PKCS1_MD4_WITH_RSA_OID;
        }
        if (name.equalsIgnoreCase("MD5withRSA")) {
            return PKCS1_MD5_WITH_RSA_OID;
        }
        if (name.equalsIgnoreCase("SHA1withRSA")) {
            return PKCS1_SHA1_WITH_RSA_OID;
        }
        if (name.equalsIgnoreCase("SHA256withRSA")) {
            return PKCS1_SHA256_WITH_RSA_OID;
        }
        if (name.equalsIgnoreCase("SHA384withRSA")) {
            return PKCS1_SHA384_WITH_RSA_OID;
        }
        if (name.equalsIgnoreCase("SHA512withRSA")) {
            return PKCS1_SHA512_WITH_RSA_OID;
        }
        if (name.equalsIgnoreCase("SHA224withRSA")) {
            return PKCS1_SHA224_WITH_RSA_OID;
        }
        if (name.equalsIgnoreCase("SHA1withECDSA")) {
            return X9_SHA1_WITH_ECDSA_OID;
        }
        if (name.equalsIgnoreCase("SHA224withECDSA")) {
            return X9_SHA224_WITH_ECDSA_OID;
        }
        if (name.equalsIgnoreCase("SHA256withECDSA")) {
            return X9_SHA256_WITH_ECDSA_OID;
        }
        if (name.equalsIgnoreCase("MGF1")) {
            return PKCS1_MGF1_OID;
        }
        if (name.equalsIgnoreCase("SHA1withRSAandMGF1")) {
            return PKCS1_RSA_PSS_OID;
        }
        if (name.equalsIgnoreCase("SHA224withRSAandMGF1")) {
            return PKCS1_RSA_PSS_OID;
        }
        if (name.equalsIgnoreCase("SHA256withRSAandMGF1")) {
            return PKCS1_RSA_PSS_OID;
        }
        if (name.equalsIgnoreCase("SHA384withRSAandMGF1")) {
            return PKCS1_RSA_PSS_OID;
        }
        if (name.equalsIgnoreCase("SHA512withRSAandMGF1")) {
            return PKCS1_RSA_PSS_OID;
        }
        throw new NoSuchAlgorithmException("Unknown name " + name);
    }
}