Java tutorial
/* * JMRTD - A Java API for accessing machine readable travel documents. * * Copyright (C) 2006 - 2014 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: Passport.java 1568 2015-01-12 20:54:05Z martijno $ */ package org.jmrtd; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.Signature; import java.security.cert.CertPath; import java.security.cert.CertPathBuilder; import java.security.cert.CertPathBuilderException; import java.security.cert.CertStore; import java.security.cert.CertStoreParameters; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXBuilderParameters; import java.security.cert.PKIXCertPathBuilderResult; import java.security.cert.TrustAnchor; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.logging.Logger; import javax.crypto.Cipher; import javax.security.auth.x500.X500Principal; import net.sf.scuba.smartcards.CardFileInputStream; import net.sf.scuba.smartcards.CardServiceException; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERSequence; import org.jmrtd.VerificationStatus.HashMatchResult; import org.jmrtd.VerificationStatus.ReasonCode; import org.jmrtd.cert.CVCPrincipal; import org.jmrtd.cert.CardVerifiableCertificate; import org.jmrtd.lds.ActiveAuthenticationInfo; import org.jmrtd.lds.COMFile; import org.jmrtd.lds.CVCAFile; import org.jmrtd.lds.CardAccessFile; import org.jmrtd.lds.ChipAuthenticationPublicKeyInfo; import org.jmrtd.lds.DG14File; import org.jmrtd.lds.DG15File; import org.jmrtd.lds.DG1File; import org.jmrtd.lds.LDS; import org.jmrtd.lds.LDSFileUtil; import org.jmrtd.lds.PACEInfo; import org.jmrtd.lds.SODFile; import org.jmrtd.lds.SecurityInfo; /** * Contains methods for creating instances from scratch, from file, and from * card service. * * Also contains the document verification logic. * * @author Wojciech Mostowski (woj@cs.ru.nl) * @author Martijn Oostdijk (martijn.oostdijk@gmail.com) * * @version $Revision: 1568 $ */ public class Passport { private static final Provider BC_PROVIDER = JMRTDSecurityProvider.getBouncyCastleProvider(); private final static List<BACKeySpec> EMPTY_TRIED_BAC_ENTRY_LIST = Collections.emptyList(); private final static List<Certificate> EMPTY_CERTIFICATE_CHAIN = Collections.emptyList(); /** The hash function for DG hashes. */ private MessageDigest digest; private FeatureStatus featureStatus; private VerificationStatus verificationStatus; /* We use a cipher to help implement Active Authentication RSA with ISO9796-2 message recovery. */ private transient Signature rsaAASignature; private transient MessageDigest rsaAADigest; private transient Cipher rsaAACipher; private transient Signature ecdsaAASignature; private transient MessageDigest ecdsaAADigest; private short cvcaFID = PassportService.EF_CVCA; private LDS lds; private static final boolean IS_PKIX_REVOCATION_CHECING_ENABLED = false; private PrivateKey docSigningPrivateKey; private CardVerifiableCertificate cvcaCertificate; private PrivateKey eacPrivateKey; private PrivateKey aaPrivateKey; private static final Logger LOGGER = Logger.getLogger("org.jmrtd"); /* * FIXME: replace trust store with something simpler. * - Move the URI interpretation functionality to clients. * - Limit public interface in Passport etc. to CertStore / KeyStore / ? extends Key / Certificate only. */ private MRTDTrustStore trustManager; private PassportService service; private Random random; private Passport() throws GeneralSecurityException { this.featureStatus = new FeatureStatus(); this.verificationStatus = new VerificationStatus(); this.random = new SecureRandom(); rsaAADigest = MessageDigest.getInstance("SHA1"); /* NOTE: for output length measurement only. -- MO */ rsaAASignature = Signature.getInstance("SHA1WithRSA/ISO9796-2", BC_PROVIDER); rsaAACipher = Cipher.getInstance("RSA/NONE/NoPadding"); /* NOTE: These will be updated in doAA after caller has read ActiveAuthenticationSecurityInfo. */ ecdsaAASignature = Signature.getInstance("SHA256withECDSA", BC_PROVIDER); ecdsaAADigest = MessageDigest.getInstance("SHA-256"); /* NOTE: for output length measurement only. -- MO */ } /** * Creates a document from an LDS data structure and additional information. * * @param lds the logical data structure * @param docSigningPrivateKey the document signing private key * @param trustManager the trust manager (CSCA, CVCA) * * @throws GeneralSecurityException if error */ public Passport(LDS lds, PrivateKey docSigningPrivateKey, MRTDTrustStore trustManager) throws GeneralSecurityException { this(); this.trustManager = trustManager; this.docSigningPrivateKey = docSigningPrivateKey; this.lds = lds; } /** * Creates a document by reading it from a service. * Access control will be BAC only. * * @param service the service to read from * @param trustManager the trust manager (CSCA, CVCA) * @param bacKey the BAC key to use * * @throws CardServiceException on error * @throws GeneralSecurityException if certain security primitives are not supported */ public Passport(PassportService service, MRTDTrustStore trustManager, BACKeySpec bacKey) throws CardServiceException, GeneralSecurityException { this(service, trustManager, Collections.singletonList(bacKey), false, false); } public Passport(PassportService service, MRTDTrustStore trustManager, BACKeySpec bacKey, boolean shouldDoPACE, boolean shouldDoBACByDefault) throws CardServiceException, GeneralSecurityException { this(service, trustManager, Collections.singletonList(bacKey), shouldDoPACE, shouldDoBACByDefault); } /** * Creates a document by reading it from a service. * * @param service the service to read from * @param trustManager the trust manager (CSCA, CVCA) * @param bacStore the BAC entries * @param shouldDoPACE whether PACE should be tried before BAC * @param shouldDoBACByDefault whether BAC should be used by default and we should not expect an unprotected document * * @throws CardServiceException on error * @throws GeneralSecurityException if certain security primitives are not supported */ public Passport(PassportService service, MRTDTrustStore trustManager, List<BACKeySpec> bacStore, boolean shouldDoPACE, boolean shouldDoBACByDefault) throws CardServiceException, GeneralSecurityException { this(); LOGGER.info("DEBUG: shouldDoBACByDefault = " + shouldDoBACByDefault); if (service == null) { throw new IllegalArgumentException("Service cannot be null"); } this.service = service; if (trustManager == null) { trustManager = new MRTDTrustStore(); } this.trustManager = trustManager; boolean hasPACE = false; boolean isPACESucceeded = false; try { service.open(); /* Find out whether this MRTD supports PACE. */ PACEInfo paceInfo = null; try { LOGGER.info("Inspecting card access file"); CardAccessFile cardAccessFile = new CardAccessFile( service.getInputStream(PassportService.EF_CARD_ACCESS)); Collection<PACEInfo> paceInfos = cardAccessFile.getPACEInfos(); LOGGER.info("DEBUG: found a card access file: paceInfos (" + (paceInfos == null ? 0 : paceInfos.size()) + ") = " + paceInfos); if (paceInfos != null && paceInfos.size() > 0) { /* FIXME: Multiple PACEInfos allowed? */ if (paceInfos.size() > 1) { LOGGER.warning("Found multiple PACEInfos " + paceInfos.size()); } paceInfo = paceInfos.iterator().next(); featureStatus.setSAC(FeatureStatus.Verdict.PRESENT); } } catch (Exception e) { /* NOTE: No card access file, continue to test for BAC. */ LOGGER.info("DEBUG: failed to get card access file: " + e.getMessage()); e.printStackTrace(); } hasPACE = featureStatus.hasSAC() == FeatureStatus.Verdict.PRESENT; if (hasPACE && shouldDoPACE) { try { isPACESucceeded = tryToDoPACE(service, paceInfo, bacStore.get(0)); // FIXME: only one bac key, DEBUG } catch (Exception e) { e.printStackTrace(); LOGGER.info("PACE failed, falling back to BAC"); isPACESucceeded = false; } } LOGGER.info("DEBUG: calling select applet with isPACESucceeded = " + isPACESucceeded); service.sendSelectApplet(isPACESucceeded); } catch (CardServiceException cse) { throw cse; } catch (Exception e) { e.printStackTrace(); throw new CardServiceException("Cannot open document. " + e.getMessage()); } String documentNumber = null; /* If PACE did not succeed find out whether we need to do BAC. */ if (!(hasPACE && isPACESucceeded)) { boolean shouldDoBAC = shouldDoBACByDefault; LOGGER.info("DEBUG: shouldDoBAC = " + shouldDoBAC); if (!shouldDoBAC) { try { /* Attempt to read EF.COM before BAC. */ LOGGER.info("DEBUG: reading first byte of EF.COM"); service.getInputStream(PassportService.EF_COM).read(); if (isPACESucceeded) { verificationStatus.setSAC(VerificationStatus.Verdict.SUCCEEDED, ReasonCode.SUCCEEDED); featureStatus.setBAC(FeatureStatus.Verdict.UNKNOWN); verificationStatus.setBAC(VerificationStatus.Verdict.NOT_CHECKED, ReasonCode.USING_SAC_SO_BAC_NOT_CHECKED, EMPTY_TRIED_BAC_ENTRY_LIST); } else { /* We failed PACE, and we don't need BAC. */ featureStatus.setBAC(FeatureStatus.Verdict.NOT_PRESENT); verificationStatus.setBAC(VerificationStatus.Verdict.NOT_PRESENT, ReasonCode.NOT_SUPPORTED, EMPTY_TRIED_BAC_ENTRY_LIST); } } catch (Exception e) { LOGGER.info("Attempt to read EF.COM before BAC failed with: " + e.getMessage()); featureStatus.setBAC(FeatureStatus.Verdict.PRESENT); verificationStatus.setBAC(VerificationStatus.Verdict.NOT_CHECKED, ReasonCode.INSUFFICIENT_CREDENTIALS, EMPTY_TRIED_BAC_ENTRY_LIST); } /* If we have to do BAC, try to do BAC. */ shouldDoBAC = featureStatus.hasBAC() == FeatureStatus.Verdict.PRESENT; } if (shouldDoBAC) { BACKeySpec bacKeySpec = tryToDoBAC(service, bacStore); if (featureStatus.hasBAC() == FeatureStatus.Verdict.UNKNOWN) { /* For some reason our test did not result in setting BAC, still apparently BAC is required. */ featureStatus.setBAC(FeatureStatus.Verdict.PRESENT); } documentNumber = bacKeySpec.getDocumentNumber(); } } this.lds = new LDS(); /* Pre-read these files that are always present. */ COMFile comFile = null; SODFile sodFile = null; DG1File dg1File = null; Collection<Integer> dgNumbersAlreadyRead = new TreeSet<Integer>(); try { CardFileInputStream comIn = service.getInputStream(PassportService.EF_COM); lds.add(PassportService.EF_COM, comIn, comIn.getLength()); comFile = lds.getCOMFile(); CardFileInputStream sodIn = service.getInputStream(PassportService.EF_SOD); lds.add(PassportService.EF_SOD, sodIn, sodIn.getLength()); sodFile = lds.getSODFile(); CardFileInputStream dg1In = service.getInputStream(PassportService.EF_DG1); lds.add(PassportService.EF_DG1, dg1In, dg1In.getLength()); dg1File = lds.getDG1File(); dgNumbersAlreadyRead.add(1); if (documentNumber == null) { documentNumber = dg1File.getMRZInfo().getDocumentNumber(); } } catch (IOException ioe) { ioe.printStackTrace(); LOGGER.warning("Could not read file"); } if (sodFile != null) { // verifyDS(); // DEBUG 2.0.4 too costly to do this on APDU thread?!?! // verifyCS(); } /* Get the list of DGs from EF.SOd, we don't trust EF.COM. */ List<Integer> dgNumbers = new ArrayList<Integer>(); if (sodFile != null) { dgNumbers.addAll(sodFile.getDataGroupHashes().keySet()); } else if (comFile != null) { /* Get the list from EF.COM since we failed to parse EF.SOd. */ LOGGER.warning("Failed to get DG list from EF.SOd. Getting DG list from EF.COM."); int[] tagList = comFile.getTagList(); dgNumbers.addAll(toDataGroupList(tagList)); } Collections.sort(dgNumbers); /* NOTE: need to sort it, since we get keys as a set. */ LOGGER.info("Found DGs: " + dgNumbers); Map<Integer, VerificationStatus.HashMatchResult> hashResults = verificationStatus.getHashResults(); if (hashResults == null) { hashResults = new TreeMap<Integer, VerificationStatus.HashMatchResult>(); } if (sodFile != null) { /* Initial hash results: we know the stored hashes, but not the computed hashes yet. */ Map<Integer, byte[]> storedHashes = sodFile.getDataGroupHashes(); for (int dgNumber : dgNumbers) { byte[] storedHash = storedHashes.get(dgNumber); VerificationStatus.HashMatchResult hashResult = hashResults.get(dgNumber); if (hashResult != null) { continue; } if (dgNumbersAlreadyRead.contains(dgNumber)) { hashResult = verifyHash(dgNumber); } else { hashResult = new HashMatchResult(storedHash, null); } hashResults.put(dgNumber, hashResult); } } verificationStatus.setHT(VerificationStatus.Verdict.UNKNOWN, verificationStatus.getHTReason(), hashResults); /* Check EAC support by DG14 presence. */ if (dgNumbers.contains(14)) { featureStatus.setEAC(FeatureStatus.Verdict.PRESENT); } else { featureStatus.setEAC(FeatureStatus.Verdict.NOT_PRESENT); } boolean hasEAC = featureStatus.hasEAC() == FeatureStatus.Verdict.PRESENT; List<KeyStore> cvcaKeyStores = trustManager.getCVCAStores(); if (hasEAC && cvcaKeyStores != null && cvcaKeyStores.size() > 0) { tryToDoEAC(service, lds, documentNumber, cvcaKeyStores); dgNumbersAlreadyRead.add(14); } /* Check AA support by DG15 presence. */ if (dgNumbers.contains(15)) { featureStatus.setAA(FeatureStatus.Verdict.PRESENT); } else { featureStatus.setAA(FeatureStatus.Verdict.NOT_PRESENT); } boolean hasAA = featureStatus.hasAA() == FeatureStatus.Verdict.PRESENT; if (hasAA) { try { CardFileInputStream dg15In = service.getInputStream(PassportService.EF_DG15); lds.add(PassportService.EF_DG15, dg15In, dg15In.getLength()); DG15File dg15File = lds.getDG15File(); dgNumbersAlreadyRead.add(15); } catch (IOException ioe) { ioe.printStackTrace(); LOGGER.warning("Could not read file"); } catch (Exception e) { verificationStatus.setAA(VerificationStatus.Verdict.NOT_CHECKED, ReasonCode.READ_ERROR_DG15_FAILURE, null); } } else { /* Feature status says: no AA, so verification status should say: no AA. */ verificationStatus.setAA(VerificationStatus.Verdict.NOT_PRESENT, ReasonCode.NOT_SUPPORTED, null); } /* Add remaining datagroups to LDS. */ for (int dgNumber : dgNumbers) { if (dgNumbersAlreadyRead.contains(dgNumber)) { continue; } if ((dgNumber == 3 || dgNumber == 4) && !verificationStatus.getEAC().equals(VerificationStatus.Verdict.SUCCEEDED)) { continue; } try { short fid = LDSFileUtil.lookupFIDByDataGroupNumber(dgNumber); CardFileInputStream cardFileInputStream = service.getInputStream(fid); lds.add(fid, cardFileInputStream, cardFileInputStream.getLength()); } catch (IOException ioe) { LOGGER.warning("Error reading DG" + dgNumber + ": " + ioe.getMessage()); break; /* out of for loop */ } catch (CardServiceException ex) { /* NOTE: Most likely EAC protected file. So log, ignore, continue with next file. */ LOGGER.info("Could not read DG" + dgNumber + ": " + ex.getMessage()); } catch (NumberFormatException nfe) { LOGGER.warning("NumberFormatException trying to get FID for DG" + dgNumber); nfe.printStackTrace(); } } } /** * Inserts a file into this document, and updates EF_COM and EF_SOd accordingly. * * @param fid the FID of the new file * @param bytes the contents of the new file */ public void putFile(short fid, byte[] bytes) { if (bytes == null) { return; } try { lds.add(fid, new ByteArrayInputStream(bytes), bytes.length); // FIXME: is this necessary? if (fid != PassportService.EF_COM && fid != PassportService.EF_SOD && fid != cvcaFID) { updateCOMSODFile(null); } } catch (IOException ioe) { ioe.printStackTrace(); } verificationStatus.setAll(VerificationStatus.Verdict.UNKNOWN, ReasonCode.UNKNOWN); // FIXME: why all? } /** * Updates EF_COM and EF_SOd using a new document signing certificate. * * @param newCertificate a certificate */ public void updateCOMSODFile(X509Certificate newCertificate) { try { COMFile comFile = lds.getCOMFile(); SODFile sodFile = lds.getSODFile(); String digestAlg = sodFile.getDigestAlgorithm(); String signatureAlg = sodFile.getDigestEncryptionAlgorithm(); X509Certificate cert = newCertificate != null ? newCertificate : sodFile.getDocSigningCertificate(); byte[] signature = sodFile.getEncryptedDigest(); Map<Integer, byte[]> dgHashes = new TreeMap<Integer, byte[]>(); List<Short> dgFids = lds.getDataGroupList(); MessageDigest digest = null; digest = MessageDigest.getInstance(digestAlg); for (Short fid : dgFids) { if (fid != PassportService.EF_COM && fid != PassportService.EF_SOD && fid != cvcaFID) { int length = lds.getLength(fid); InputStream inputStream = lds.getInputStream(fid); if (inputStream == null) { LOGGER.warning("Could not get input stream for " + Integer.toHexString(fid)); continue; } DataInputStream dataInputStream = new DataInputStream(inputStream); byte[] data = new byte[length]; dataInputStream.readFully(data); byte tag = data[0]; dgHashes.put(LDSFileUtil.lookupDataGroupNumberByTag(tag), digest.digest(data)); comFile.insertTag((int) (tag & 0xFF)); } } if (docSigningPrivateKey != null) { sodFile = new SODFile(digestAlg, signatureAlg, dgHashes, docSigningPrivateKey, cert); } else { sodFile = new SODFile(digestAlg, signatureAlg, dgHashes, signature, cert); } lds.add(comFile); lds.add(sodFile); } catch (Exception e) { e.printStackTrace(); } } public LDS getLDS() { return lds; } /** * Sets the document signing private key. * * @param docSigningPrivateKey a private key */ public void setDocSigningPrivateKey(PrivateKey docSigningPrivateKey) { this.docSigningPrivateKey = docSigningPrivateKey; updateCOMSODFile(null); } /** * Gets the CVCA certificate. * * @return a CV certificate or <code>null</code> */ public CardVerifiableCertificate getCVCertificate() { return cvcaCertificate; } /** * Sets the CVCA certificate. * * @param cert the CV certificate */ public void setCVCertificate(CardVerifiableCertificate cert) { this.cvcaCertificate = cert; try { CVCAFile cvcaFile = new CVCAFile(cvcaFID, cvcaCertificate.getHolderReference().getName()); putFile(cvcaFID, cvcaFile.getEncoded()); } catch (CertificateException ce) { ce.printStackTrace(); } } /** * Gets the document signing private key, or <code>null</code> if not present. * * @return a private key or <code>null</code> */ public PrivateKey getDocSigningPrivateKey() { return docSigningPrivateKey; } /** * Sets the document signing certificate. * * @param docSigningCertificate a certificate */ public void setDocSigningCertificate(X509Certificate docSigningCertificate) { updateCOMSODFile(docSigningCertificate); } /** * Gets the CSCA, CVCA trust store. * * @return the trust store in use */ public MRTDTrustStore getTrustManager() { return trustManager; } /** * Gets the private key for EAC, or <code>null</code> if not present. * * @return a private key or <code>null</code> */ public PrivateKey getEACPrivateKey() { return eacPrivateKey; } /** * Sets the private key for EAC. * * @param eacPrivateKey a private key */ public void setEACPrivateKey(PrivateKey eacPrivateKey) { this.eacPrivateKey = eacPrivateKey; } /** * Sets the public key for EAC. * * @param eacPublicKey a public key */ public void setEACPublicKey(PublicKey eacPublicKey) { ChipAuthenticationPublicKeyInfo chipAuthenticationPublicKeyInfo = new ChipAuthenticationPublicKeyInfo( eacPublicKey); DG14File dg14File = new DG14File(Arrays.asList(new SecurityInfo[] { chipAuthenticationPublicKeyInfo })); putFile(PassportService.EF_DG14, dg14File.getEncoded()); } /** * Gets the private key for AA, or <code>null</code> if not present. * * @return a private key or <code>null</code> */ public PrivateKey getAAPrivateKey() { return aaPrivateKey; } /** * Sets the private key for AA. * * @param aaPrivateKey a private key */ public void setAAPrivateKey(PrivateKey aaPrivateKey) { this.aaPrivateKey = aaPrivateKey; } /** * Sets the public key for AA. * * @param aaPublicKey a public key */ public void setAAPublicKey(PublicKey aaPublicKey) { DG15File dg15file = new DG15File(aaPublicKey); putFile(PassportService.EF_DG15, dg15file.getEncoded()); } /** * Gets the supported features (such as: BAC, AA, EAC) as * discovered during initialization of this document. * * @return the supported features * * @since 0.4.9 */ public FeatureStatus getFeatures() { /* The feature status has been created in constructor. */ return featureStatus; } /** * Gets the verification status thus far. * * @return the verification status * * @since 0.4.9 */ public VerificationStatus getVerificationStatus() { return verificationStatus; } /* ONLY PRIVATE METHODS BELOW. */ private BACKeySpec tryToDoBAC(PassportService service, List<BACKeySpec> bacStore) throws BACDeniedException { List<BACKeySpec> triedBACEntries = new ArrayList<BACKeySpec>(); int lastKnownSW = BACDeniedException.SW_NONE; synchronized (bacStore) { for (BACKeySpec bacKey : bacStore) { try { triedBACEntries.add(bacKey); tryToDoBAC(service, bacKey); verificationStatus.setBAC(VerificationStatus.Verdict.SUCCEEDED, ReasonCode.SUCCEEDED, triedBACEntries); return bacKey; } catch (CardServiceException cse) { LOGGER.info("Ignoring the following exception: " + cse.getClass().getCanonicalName()); cse.printStackTrace(); // DEBUG: this line was commented in production lastKnownSW = cse.getSW(); /* NOTE: BAC failed? Try next BACEntry */ } } } /* Document requires BAC, but we failed to authenticate. */ verificationStatus.setBAC(VerificationStatus.Verdict.FAILED, ReasonCode.INSUFFICIENT_CREDENTIALS, triedBACEntries); throw new BACDeniedException("Basic Access denied!", triedBACEntries, lastKnownSW); } private void tryToDoBAC(PassportService service, BACKeySpec bacKey) throws CardServiceException { try { LOGGER.info("Trying BAC: " + bacKey); service.doBAC(bacKey); /* NOTE: if successful, doBAC te catch (CardServiceException cse) { e.thrrminates normally, otherwise exception. */ } catch (Exception e) { if (e instanceof CardServiceException) { throw (CardServiceException) e; } LOGGER.warning("DEBUG: Unexpected exception " + e.getClass().getCanonicalName() + " during BAC with " + bacKey); e.printStackTrace(); throw new CardServiceException(e.getMessage()); } } private boolean tryToDoPACE(PassportService service, PACEInfo paceInfo, BACKeySpec bacKey) throws CardServiceException { // LOGGER.info("DEBUG: PACE has been disabled in this version of JMRTD"); // return false; LOGGER.info("DEBUG: attempting doPACE with PACEInfo " + paceInfo); service.doPACE(bacKey, paceInfo.getObjectIdentifier(), PACEInfo.toParameterSpec(paceInfo.getParameterId())); return true; } private void tryToDoEAC(PassportService service, LDS lds, String documentNumber, List<KeyStore> cvcaKeyStores) throws CardServiceException { DG14File dg14File = null; CVCAFile cvcaFile = null; try { try { /* Make sure DG14 is read. */ CardFileInputStream dg14In = service.getInputStream(PassportService.EF_DG14); lds.add(PassportService.EF_DG14, dg14In, dg14In.getLength()); dg14File = lds.getDG14File(); /* Now try to deal with EF.CVCA. */ cvcaFID = PassportService.EF_CVCA; /* Default CVCA file Id */ List<Short> cvcaFIDs = dg14File.getCVCAFileIds(); if (cvcaFIDs != null && cvcaFIDs.size() != 0) { if (cvcaFIDs.size() > 1) { LOGGER.warning("More than one CVCA file id present in DG14"); } cvcaFID = cvcaFIDs.get(0).shortValue(); /* Possibly different from default. */ } CardFileInputStream cvcaIn = service.getInputStream(cvcaFID); lds.add(cvcaFID, cvcaIn, cvcaIn.getLength()); cvcaFile = lds.getCVCAFile(); } catch (IOException ioe) { ioe.printStackTrace(); LOGGER.warning("Could not read EF.DG14 or EF.CVCA, not attempting EAC"); return; } /* Try to do EAC. */ CVCPrincipal[] possibleCVCAReferences = new CVCPrincipal[] { cvcaFile.getCAReference(), cvcaFile.getAltCAReference() }; for (CVCPrincipal caReference : possibleCVCAReferences) { EACCredentials eacCredentials = getEACCredentials(caReference, cvcaKeyStores); if (eacCredentials == null) { continue; } PrivateKey privateKey = eacCredentials.getPrivateKey(); Certificate[] chain = eacCredentials.getChain(); List<CardVerifiableCertificate> terminalCerts = new ArrayList<CardVerifiableCertificate>( chain.length); for (Certificate c : chain) { terminalCerts.add((CardVerifiableCertificate) c); } Map<BigInteger, PublicKey> cardKeys = dg14File.getChipAuthenticationPublicKeyInfos(); for (Map.Entry<BigInteger, PublicKey> entry : cardKeys.entrySet()) { BigInteger keyId = entry.getKey(); PublicKey publicKey = entry.getValue(); try { ChipAuthenticationResult chipAuthenticationResult = service.doCA(keyId, publicKey); TerminalAuthenticationResult eacResult = service.doTA(caReference, terminalCerts, privateKey, null, chipAuthenticationResult, documentNumber); verificationStatus.setEAC(VerificationStatus.Verdict.SUCCEEDED, ReasonCode.SUCCEEDED, eacResult); } catch (CardServiceException cse) { cse.printStackTrace(); /* NOTE: Failed? Too bad, try next public key. */ continue; } } break; } } catch (Exception e) { LOGGER.warning("EAC failed with exception " + e.getMessage()); e.printStackTrace(); } } /** * Encapsulates the terminal key and associated certificte chain for terminal authentication. */ class EACCredentials { private PrivateKey privateKey; private Certificate[] chain; /** * Creates EAC credentials. * * @param privateKey * @param chain */ public EACCredentials(PrivateKey privateKey, Certificate[] chain) { this.privateKey = privateKey; this.chain = chain; } public PrivateKey getPrivateKey() { return privateKey; } public Certificate[] getChain() { return chain; } } private EACCredentials getEACCredentials(CVCPrincipal caReference, List<KeyStore> cvcaStores) throws GeneralSecurityException { for (KeyStore cvcaStore : cvcaStores) { EACCredentials eacCredentials = getEACCredentials(caReference, cvcaStore); if (eacCredentials != null) { return eacCredentials; } } return null; } /** * Searches the key store for a relevant terminal key and associated certificate chain. * * @param caReference * @param cvcaStore should contain a single key with certificate chain * @return * @throws GeneralSecurityException */ private EACCredentials getEACCredentials(CVCPrincipal caReference, KeyStore cvcaStore) throws GeneralSecurityException { if (caReference == null) { throw new IllegalArgumentException("CA reference cannot be null"); } PrivateKey privateKey = null; Certificate[] chain = null; List<String> aliases = Collections.list(cvcaStore.aliases()); for (String alias : aliases) { if (cvcaStore.isKeyEntry(alias)) { Security.insertProviderAt(BC_PROVIDER, 0); Key key = cvcaStore.getKey(alias, "".toCharArray()); if (key instanceof PrivateKey) { privateKey = (PrivateKey) key; } else { LOGGER.warning("skipping non-private key " + alias); continue; } chain = cvcaStore.getCertificateChain(alias); return new EACCredentials(privateKey, chain); } else if (cvcaStore.isCertificateEntry(alias)) { CardVerifiableCertificate certificate = (CardVerifiableCertificate) cvcaStore.getCertificate(alias); CVCPrincipal authRef = certificate.getAuthorityReference(); CVCPrincipal holderRef = certificate.getHolderReference(); if (!caReference.equals(authRef)) { continue; } /* See if we have a private key for that certificate. */ privateKey = (PrivateKey) cvcaStore.getKey(holderRef.getName(), "".toCharArray()); chain = cvcaStore.getCertificateChain(holderRef.getName()); if (privateKey == null) { continue; } LOGGER.fine("found a key, privateKey = " + privateKey); return new EACCredentials(privateKey, chain); } if (privateKey == null || chain == null) { LOGGER.severe("null chain or key for entry " + alias + ": chain = " + Arrays.toString(chain) + ", privateKey = " + privateKey); continue; } } return null; } /** * Builds a certificate chain to an anchor using the PKIX algorithm. * * @param docSigningCertificate the start certificate * @param sodIssuer the issuer of the start certificate (ignored unless <code>docSigningCertificate</code> is <code>null</code>) * @param sodSerialNumber the serial number of the start certificate (ignored unless <code>docSigningCertificate</code> is <code>null</code>) * * @return the certificate chain */ private static List<Certificate> getCertificateChain(X509Certificate docSigningCertificate, final X500Principal sodIssuer, final BigInteger sodSerialNumber, List<CertStore> cscaStores, Set<TrustAnchor> cscaTrustAnchors) { List<Certificate> chain = new ArrayList<Certificate>(); X509CertSelector selector = new X509CertSelector(); try { if (docSigningCertificate != null) { selector.setCertificate(docSigningCertificate); } else { selector.setIssuer(sodIssuer); selector.setSerialNumber(sodSerialNumber); } CertStoreParameters docStoreParams = new CollectionCertStoreParameters( Collections.singleton((Certificate) docSigningCertificate)); CertStore docStore = CertStore.getInstance("Collection", docStoreParams); CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", BC_PROVIDER); PKIXBuilderParameters buildParams = new PKIXBuilderParameters(cscaTrustAnchors, selector); buildParams.addCertStore(docStore); for (CertStore trustStore : cscaStores) { buildParams.addCertStore(trustStore); } buildParams.setRevocationEnabled( IS_PKIX_REVOCATION_CHECING_ENABLED); /* NOTE: set to false for checking disabled. */ Security.addProvider( BC_PROVIDER); /* DEBUG: needed, or builder will throw a runtime exception. FIXME! */ PKIXCertPathBuilderResult result = null; try { result = (PKIXCertPathBuilderResult) builder.build(buildParams); } catch (CertPathBuilderException cpbe) { /* NOTE: ignore, result remain null */ } if (result != null) { CertPath pkixCertPath = result.getCertPath(); if (pkixCertPath != null) { chain.addAll(pkixCertPath.getCertificates()); } } if (docSigningCertificate != null && !chain.contains(docSigningCertificate)) { /* NOTE: if doc signing certificate not in list, we add it ourselves. */ LOGGER.warning("Adding doc signing certificate after PKIXBuilder finished"); chain.add(0, docSigningCertificate); } if (result != null) { Certificate trustAnchorCertificate = result.getTrustAnchor().getTrustedCert(); if (trustAnchorCertificate != null && !chain.contains(trustAnchorCertificate)) { /* NOTE: if trust anchor not in list, we add it ourselves. */ LOGGER.warning("Adding trust anchor certificate after PKIXBuilder finished"); chain.add(trustAnchorCertificate); } } } catch (Exception e) { e.printStackTrace(); LOGGER.info("Building a chain failed (" + e.getMessage() + ")."); } return chain; } /** * Check active authentication. */ public void verifyAA() { int challengeLength = 8; byte[] challenge = new byte[challengeLength]; random.nextBytes(challenge); ActiveAuthenticationResult aaResult = executeAA(challenge); verifyAA(aaResult); } /** * Execute active authentication using the given challenge. * * @param challenge an byte array of length 8 */ public ActiveAuthenticationResult executeAA(byte[] challenge) { if (lds == null || service == null) { verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.UNKNOWN, null); return null; } try { DG15File dg15File = lds.getDG15File(); if (dg15File == null) { verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.READ_ERROR_DG15_FAILURE, null); return null; } PublicKey pubKey = dg15File.getPublicKey(); String pubKeyAlgorithm = pubKey.getAlgorithm(); String digestAlgorithm = "SHA1"; String signatureAlgorithm = "SHA1WithRSA/ISO9796-2"; if ("EC".equals(pubKeyAlgorithm) || "ECDSA".equals(pubKeyAlgorithm)) { DG14File dg14File = lds.getDG14File(); if (dg14File == null) { verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.READ_ERROR_DG14_FAILURE, null); return null; } List<ActiveAuthenticationInfo> activeAuthenticationInfos = dg14File.getActiveAuthenticationInfos(); int activeAuthenticationInfoCount = (activeAuthenticationInfos == null ? 0 : activeAuthenticationInfos.size()); if (activeAuthenticationInfoCount < 1) { verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.READ_ERROR_DG14_FAILURE, null); return null; } else if (activeAuthenticationInfoCount > 1) { LOGGER.warning("Found " + activeAuthenticationInfoCount + " in EF.DG14, expected 1."); } ActiveAuthenticationInfo activeAuthenticationInfo = activeAuthenticationInfos.get(0); String signatureAlgorithmOID = activeAuthenticationInfo.getSignatureAlgorithmOID(); signatureAlgorithm = ActiveAuthenticationInfo.lookupMnemonicByOID(signatureAlgorithmOID); digestAlgorithm = Util.inferDigestAlgorithmFromSignatureAlgorithm(signatureAlgorithm); } byte[] response = service.doAA(pubKey, digestAlgorithm, signatureAlgorithm, challenge); return new ActiveAuthenticationResult(pubKey, digestAlgorithm, signatureAlgorithm, challenge, response); } catch (CardServiceException cse) { cse.printStackTrace(); verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.UNEXPECTED_EXCEPTION_FAILURE, null); return null; } catch (Exception e) { LOGGER.severe("DEBUG: this exception wasn't caught in verification logic (< 0.4.8) -- MO 3. Type is " + e.getClass().getCanonicalName()); e.printStackTrace(); verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.UNEXPECTED_EXCEPTION_FAILURE, null); return null; } } /** * Check the active authentication result. * * @param aaResult * @return */ public boolean verifyAA(ActiveAuthenticationResult aaResult) { try { PublicKey publicKey = aaResult.getPublicKey(); String digestAlgorithm = aaResult.getDigestAlgorithm(); String signatureAlgorithm = aaResult.getSignatureAlgorithm(); byte[] challenge = aaResult.getChallenge(); byte[] response = aaResult.getResponse(); String pubKeyAlgorithm = publicKey.getAlgorithm(); if ("RSA".equals(pubKeyAlgorithm)) { /* FIXME: check that digestAlgorithm = "SHA1" in this case, check (and re-initialize) rsaAASignature (and rsaAACipher). */ if (!"SHA1".equalsIgnoreCase(digestAlgorithm) || !"SHA-1".equalsIgnoreCase(digestAlgorithm) || !"SHA1WithRSA/ISO9796-2".equalsIgnoreCase(signatureAlgorithm)) { LOGGER.warning("Unexpected algorithms for RSA AA: " + "digest algorithm = " + (digestAlgorithm == null ? "null" : digestAlgorithm) + ", signature algorithm = " + (signatureAlgorithm == null ? "null" : signatureAlgorithm)); rsaAADigest = MessageDigest .getInstance(digestAlgorithm); /* NOTE: for output length measurement only. -- MO */ rsaAASignature = Signature.getInstance(signatureAlgorithm, BC_PROVIDER); } RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey; rsaAACipher.init(Cipher.DECRYPT_MODE, rsaPublicKey); rsaAASignature.initVerify(rsaPublicKey); int digestLength = rsaAADigest.getDigestLength(); /* SHA1 should be 20 bytes = 160 bits */ assert (digestLength == 20); byte[] plaintext = rsaAACipher.doFinal(response); byte[] m1 = Util.recoverMessage(digestLength, plaintext); rsaAASignature.update(m1); rsaAASignature.update(challenge); boolean success = rsaAASignature.verify(response); if (success) { verificationStatus.setAA(VerificationStatus.Verdict.SUCCEEDED, ReasonCode.SIGNATURE_CHECKED, aaResult); } else { verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.SIGNATURE_FAILURE, aaResult); } return success; } else if ("EC".equals(pubKeyAlgorithm) || "ECDSA".equals(pubKeyAlgorithm)) { ECPublicKey ecdsaPublicKey = (ECPublicKey) publicKey; if (ecdsaAASignature == null || signatureAlgorithm != null && !signatureAlgorithm.equals(ecdsaAASignature.getAlgorithm())) { LOGGER.warning( "Re-initializing ecdsaAASignature with signature algorithm " + signatureAlgorithm); ecdsaAASignature = Signature.getInstance(signatureAlgorithm); } if (ecdsaAADigest == null || digestAlgorithm != null && !digestAlgorithm.equals(ecdsaAADigest.getAlgorithm())) { LOGGER.warning("Re-initializing ecdsaAADigest with digest algorithm " + digestAlgorithm); ecdsaAADigest = MessageDigest.getInstance(digestAlgorithm); } ecdsaAASignature.initVerify(ecdsaPublicKey); if (response.length % 2 != 0) { LOGGER.warning("Active Authentication response is not of even length"); } int l = response.length / 2; BigInteger r = Util.os2i(response, 0, l); BigInteger s = Util.os2i(response, l, l); ecdsaAASignature.update(challenge); try { ASN1Sequence asn1Sequence = new DERSequence( new ASN1Encodable[] { new ASN1Integer(r), new ASN1Integer(s) }); boolean success = ecdsaAASignature.verify(asn1Sequence.getEncoded()); if (success) { verificationStatus.setAA(VerificationStatus.Verdict.SUCCEEDED, ReasonCode.SUCCEEDED, aaResult); } else { verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.SIGNATURE_FAILURE, aaResult); } return success; } catch (IOException ioe) { LOGGER.severe("Unexpected exception during AA signature verification with ECDSA"); ioe.printStackTrace(); verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.UNEXPECTED_EXCEPTION_FAILURE, aaResult); return false; } } else { LOGGER.severe("Unsupported AA public key type " + publicKey.getClass().getSimpleName()); verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.UNSUPPORTED_KEY_TYPE_FAILURE, aaResult); return false; } } catch (Exception e) { verificationStatus.setAA(VerificationStatus.Verdict.FAILED, ReasonCode.UNEXPECTED_EXCEPTION_FAILURE, aaResult); return false; } } /** * Checks the security object's signature. * * TODO: Check the cert stores (notably PKD) to fetch document signer certificate (if not embedded in SOd) and check its validity before checking the signature. */ public void verifyDS() { try { verificationStatus.setDS(VerificationStatus.Verdict.UNKNOWN, ReasonCode.UNKNOWN); SODFile sod = lds.getSODFile(); /* Check document signing signature. */ X509Certificate docSigningCert = sod.getDocSigningCertificate(); if (docSigningCert == null) { LOGGER.warning("Could not get document signer certificate from EF.SOd"); // FIXME: We search for it in cert stores. See note at verifyCS. // X500Principal issuer = sod.getIssuerX500Principal(); // BigInteger serialNumber = sod.getSerialNumber(); } if (sod.checkDocSignature(docSigningCert)) { verificationStatus.setDS(VerificationStatus.Verdict.SUCCEEDED, ReasonCode.SIGNATURE_CHECKED); } else { verificationStatus.setDS(VerificationStatus.Verdict.FAILED, ReasonCode.SIGNATURE_FAILURE); } } catch (NoSuchAlgorithmException nsae) { verificationStatus.setDS(VerificationStatus.Verdict.FAILED, ReasonCode.UNSUPPORTED_SIGNATURE_ALGORITHM_FAILURE); return; /* NOTE: Serious enough to not perform other checks, leave method. */ } catch (Exception e) { e.printStackTrace(); verificationStatus.setDS(VerificationStatus.Verdict.FAILED, ReasonCode.UNEXPECTED_EXCEPTION_FAILURE); return; /* NOTE: Serious enough to not perform other checks, leave method. */ } } /** * Checks the certificate chain. */ public void verifyCS() { try { /* Get EF.SOd. */ SODFile sod = null; try { sod = lds.getSODFile(); } catch (IOException ioe) { LOGGER.severe("Could not read EF.SOd"); } List<Certificate> chain = new ArrayList<Certificate>(); if (sod == null) { verificationStatus.setCS(VerificationStatus.Verdict.FAILED, ReasonCode.COULD_NOT_BUILD_CHAIN_FAILURE, chain); return; } /* Get doc signing certificate and issuer info. */ X509Certificate docSigningCertificate = null; X500Principal sodIssuer = null; BigInteger sodSerialNumber = null; try { sodIssuer = sod.getIssuerX500Principal(); sodSerialNumber = sod.getSerialNumber(); docSigningCertificate = sod.getDocSigningCertificate(); } catch (Exception e) { LOGGER.warning("Error getting document signing certificate: " + e.getMessage()); // FIXME: search for it in cert stores? } if (docSigningCertificate != null) { chain.add(docSigningCertificate); } else { LOGGER.warning("Error getting document signing certificate from EF.SOd"); } /* Get trust anchors. */ List<CertStore> cscaStores = trustManager.getCSCAStores(); if (cscaStores == null || cscaStores.size() <= 0) { LOGGER.warning("No CSCA certificate stores found."); verificationStatus.setCS(VerificationStatus.Verdict.FAILED, ReasonCode.NO_CSCA_TRUST_ANCHORS_FOUND_FAILURE, chain); } Set<TrustAnchor> cscaTrustAnchors = trustManager.getCSCAAnchors(); if (cscaTrustAnchors == null || cscaTrustAnchors.size() <= 0) { LOGGER.warning("No CSCA trust anchors found."); verificationStatus.setCS(VerificationStatus.Verdict.FAILED, ReasonCode.NO_CSCA_TRUST_ANCHORS_FOUND_FAILURE, chain); } /* Optional internal EF.SOd consistency check. */ if (docSigningCertificate != null) { X500Principal docIssuer = docSigningCertificate.getIssuerX500Principal(); if (sodIssuer != null && !sodIssuer.equals(docIssuer)) { LOGGER.severe( "Security object issuer principal is different from embedded DS certificate issuer!"); } BigInteger docSerialNumber = docSigningCertificate.getSerialNumber(); if (sodSerialNumber != null && !sodSerialNumber.equals(docSerialNumber)) { LOGGER.warning( "Security object serial number is different from embedded DS certificate serial number!"); } } /* Run PKIX algorithm to build chain to any trust anchor. Add certificates to our chain. */ List<Certificate> pkixChain = getCertificateChain(docSigningCertificate, sodIssuer, sodSerialNumber, cscaStores, cscaTrustAnchors); if (pkixChain == null) { verificationStatus.setCS(VerificationStatus.Verdict.FAILED, ReasonCode.SIGNATURE_FAILURE, chain); return; } for (Certificate certificate : pkixChain) { if (certificate.equals(docSigningCertificate)) { continue; } /* Ignore DS certificate, which is already in chain. */ chain.add(certificate); } int chainDepth = chain.size(); if (chainDepth <= 1) { verificationStatus.setCS(VerificationStatus.Verdict.FAILED, ReasonCode.COULD_NOT_BUILD_CHAIN_FAILURE, chain); return; } if (chainDepth > 1 && verificationStatus.getCS().equals(VerificationStatus.Verdict.UNKNOWN)) { verificationStatus.setCS(VerificationStatus.Verdict.SUCCEEDED, ReasonCode.FOUND_A_CHAIN_SUCCEEDED, chain); } } catch (Exception e) { e.printStackTrace(); verificationStatus.setCS(VerificationStatus.Verdict.FAILED, ReasonCode.SIGNATURE_FAILURE, EMPTY_CERTIFICATE_CHAIN); } } /** * Checks hashes in the SOd correspond to hashes we compute. */ public void verifyHT() { /* Compare stored hashes to computed hashes. */ Map<Integer, VerificationStatus.HashMatchResult> hashResults = verificationStatus.getHashResults(); if (hashResults == null) { hashResults = new TreeMap<Integer, VerificationStatus.HashMatchResult>(); } SODFile sod = null; try { sod = lds.getSODFile(); } catch (Exception e) { verificationStatus.setHT(VerificationStatus.Verdict.FAILED, ReasonCode.READ_ERROR_SOD_FAILURE, hashResults); return; } Map<Integer, byte[]> storedHashes = sod.getDataGroupHashes(); for (int dgNumber : storedHashes.keySet()) { verifyHash(dgNumber, hashResults); } if (verificationStatus.getHT().equals(VerificationStatus.Verdict.UNKNOWN)) { verificationStatus.setHT(VerificationStatus.Verdict.SUCCEEDED, ReasonCode.ALL_HASHES_MATCH, hashResults); } else { /* Update storedHashes and computedHashes. */ verificationStatus.setHT(verificationStatus.getHT(), verificationStatus.getHTReason(), hashResults); } } private HashMatchResult verifyHash(int dgNumber) { Map<Integer, VerificationStatus.HashMatchResult> hashResults = verificationStatus.getHashResults(); if (hashResults == null) { hashResults = new TreeMap<Integer, VerificationStatus.HashMatchResult>(); } return verifyHash(dgNumber, hashResults); } /** * Verifies the hash for the given datagroup. * Note that this will block until all bytes of the datagroup * are loaded. * * @param dgNumber * @param digest an existing digest that will be reused (this method will reset it) * @param storedHash the stored hash for this datagroup * @param hashResults the hashtable status to update */ private VerificationStatus.HashMatchResult verifyHash(int dgNumber, Map<Integer, VerificationStatus.HashMatchResult> hashResults) { short fid = LDSFileUtil.lookupFIDByTag(LDSFileUtil.lookupTagByDataGroupNumber(dgNumber)); SODFile sod = null; /* Get the stored hash for the DG. */ byte[] storedHash = null; try { sod = lds.getSODFile(); Map<Integer, byte[]> storedHashes = sod.getDataGroupHashes(); storedHash = storedHashes.get(dgNumber); } catch (Exception e) { verificationStatus.setHT(VerificationStatus.Verdict.FAILED, ReasonCode.STORED_HASH_NOT_FOUND_FAILURE, hashResults); return null; } /* Initialize hash. */ String digestAlgorithm = sod.getDigestAlgorithm(); try { digest = getDigest(digestAlgorithm); } catch (NoSuchAlgorithmException nsae) { verificationStatus.setHT(VerificationStatus.Verdict.FAILED, ReasonCode.UNSUPPORTED_DIGEST_ALGORITHM_FAILURE, null); return null; // DEBUG -- MO } /* Read the DG. */ byte[] dgBytes = null; try { InputStream dgIn = null; int length = lds.getLength(fid); if (length > 0) { dgBytes = new byte[length]; dgIn = lds.getInputStream(fid); DataInputStream dgDataIn = new DataInputStream(dgIn); dgDataIn.readFully(dgBytes); } if (dgIn == null && (verificationStatus.getEAC() != VerificationStatus.Verdict.SUCCEEDED) && (fid == PassportService.EF_DG3 || fid == PassportService.EF_DG4)) { LOGGER.warning("Skipping DG" + dgNumber + " during HT verification because EAC failed."); VerificationStatus.HashMatchResult hashResult = new HashMatchResult(storedHash, null); hashResults.put(dgNumber, hashResult); return hashResult; } if (dgIn == null) { LOGGER.warning( "Skipping DG" + dgNumber + " during HT verification because file could not be read."); VerificationStatus.HashMatchResult hashResult = new HashMatchResult(storedHash, null); hashResults.put(dgNumber, hashResult); return hashResult; } } catch (Exception e) { VerificationStatus.HashMatchResult hashResult = new HashMatchResult(storedHash, null); hashResults.put(dgNumber, hashResult); verificationStatus.setHT(VerificationStatus.Verdict.FAILED, ReasonCode.UNEXPECTED_EXCEPTION_FAILURE, hashResults); return hashResult; } /* Compute the hash and compare. */ try { byte[] computedHash = digest.digest(dgBytes); VerificationStatus.HashMatchResult hashResult = new HashMatchResult(storedHash, computedHash); hashResults.put(dgNumber, hashResult); if (!Arrays.equals(storedHash, computedHash)) { verificationStatus.setHT(VerificationStatus.Verdict.FAILED, ReasonCode.HASH_MISMATCH_FAILURE, hashResults); } return hashResult; } catch (Exception ioe) { VerificationStatus.HashMatchResult hashResult = new HashMatchResult(storedHash, null); hashResults.put(dgNumber, hashResult); verificationStatus.setHT(VerificationStatus.Verdict.FAILED, ReasonCode.UNEXPECTED_EXCEPTION_FAILURE, hashResults); return hashResult; } } private MessageDigest getDigest(String digestAlgorithm) throws NoSuchAlgorithmException { if (digest != null) { digest.reset(); return digest; } LOGGER.info("Using hash algorithm " + digestAlgorithm); if (Security.getAlgorithms("MessageDigest").contains(digestAlgorithm)) { digest = MessageDigest.getInstance(digestAlgorithm); } else { digest = MessageDigest.getInstance(digestAlgorithm, BC_PROVIDER); } return digest; } private List<Integer> toDataGroupList(int[] tagList) { if (tagList == null) { return null; } List<Integer> dgNumberList = new ArrayList<Integer>(tagList.length); for (int tag : tagList) { try { int dgNumber = LDSFileUtil.lookupDataGroupNumberByTag(tag); dgNumberList.add(dgNumber); } catch (NumberFormatException nfe) { LOGGER.warning("Could not find DG number for tag: " + Integer.toHexString(tag)); nfe.printStackTrace(); } } return dgNumberList; } }