org.jmrtd.lds.CardSecurityFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmrtd Show documentation
Show all versions of jmrtd Show documentation
Java Machine Readable Travel Documents API.
/*
* JMRTD - A Java API for accessing machine readable travel documents.
*
* Copyright (C) 2006 - 2017 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: CardSecurityFile.java 1701 2017-09-04 18:17:07Z martijno $
*/
package org.jmrtd.lds;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DLSet;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.SignedData;
/**
* Card security file stores a set of SecurityInfos for PACE with Chip Authentication Mapping (CAM).
*
* FIXME: Strictly speaking this file is not part of the LDS (or even the MRTD application)! Move it out of this package? -- MO
*
* @author The JMRTD team ([email protected])
*
* @version $Revision: 1701 $
*
* @since 0.5.6
*/
public class CardSecurityFile implements Serializable {
private static final long serialVersionUID = -3535507558193769952L;
private static final Logger LOGGER = Logger.getLogger("org.jmrtd");
private static final String CONTENT_TYPE_OID = "0.4.0.127.0.7.3.2.1"; // FIXME
private String digestAlgorithm;
private String digestEncryptionAlgorithm;
/** The security infos that make up this file. */
private Set securityInfos;
/** The signature bytes. */
private byte[] encryptedDigest;
/** The embedded document signer certificate. */
private X509Certificate certificate;
/**
* Constructs a new file from the provided data.
*
* @param digestAlgorithm the digest algorithm as Java mnemonic
* @param digestEncryptionAlgorithm the signature algorithm as Java mnemonic
* @param securityInfos a non-empty list of security infos
* @param privateKey the private signing key
* @param certificate the certificate to embed, which should correspond to the given private key
*/
public CardSecurityFile(String digestAlgorithm, String digestEncryptionAlgorithm, Collection securityInfos, PrivateKey privateKey, X509Certificate certificate) {
this(digestAlgorithm, digestEncryptionAlgorithm, securityInfos, privateKey, certificate, null);
}
/**
* Constructs a new file from the provided data.
*
* @param digestAlgorithm the digest algorithm as Java mnemonic
* @param digestEncryptionAlgorithm the signature algorithm as Java mnemonic
* @param securityInfos a non-empty list of security infos
* @param privateKey the private signing key
* @param certificate the certificate to embed, which should correspond to the given private key
* @param provider the security provider to use
*/
public CardSecurityFile(String digestAlgorithm, String digestEncryptionAlgorithm, Collection securityInfos, PrivateKey privateKey, X509Certificate certificate, String provider) {
this(digestAlgorithm, digestEncryptionAlgorithm, securityInfos, (byte[])null, certificate);
ContentInfo contentInfo = toContentInfo(CONTENT_TYPE_OID, securityInfos);
this.encryptedDigest = SignedDataUtil.signData(digestAlgorithm, digestEncryptionAlgorithm, CONTENT_TYPE_OID, contentInfo, privateKey, provider);
}
/**
* Constructs a new file from the provided data.
*
* @param digestAlgorithm the digest algorithm as Java mnemonic
* @param digestEncryptionAlgorithm the signature algorithm as Java mnemonic
* @param securityInfos a non-empty list of security infos
* @param encryptedDigest the signature
* @param certificate the certificate to embed
*/
public CardSecurityFile(String digestAlgorithm, String digestEncryptionAlgorithm, Collection securityInfos, byte[] encryptedDigest, X509Certificate certificate) {
if (securityInfos == null) {
throw new IllegalArgumentException("Null securityInfos");
}
if (certificate == null) {
throw new IllegalArgumentException("Null certificate");
}
this.digestAlgorithm = digestAlgorithm;
this.digestEncryptionAlgorithm = digestEncryptionAlgorithm;
this.securityInfos = new HashSet(securityInfos);
this.encryptedDigest = encryptedDigest;
this.certificate = certificate;
}
/**
* Constructs a new file from the data in an input stream.
*
* @param inputStream the input stream to parse the data from
*
* @throws IOException on error reading input stream
*/
public CardSecurityFile(InputStream inputStream) throws IOException {
readContent(inputStream);
}
public String getDigestAlgorithm() {
return digestAlgorithm;
}
public String getDigestEncryptionAlgorithm() {
return digestEncryptionAlgorithm;
}
public byte[] getEncryptedDigest() {
return encryptedDigest;
}
protected void readContent(InputStream inputStream) throws IOException {
SignedData signedData = SignedDataUtil.readSignedData(inputStream);
this.digestAlgorithm = SignedDataUtil.getSignerInfoDigestAlgorithm(signedData);
this.digestEncryptionAlgorithm = SignedDataUtil.getDigestEncryptionAlgorithm(signedData);
try {
this.certificate = SignedDataUtil.getDocSigningCertificate(signedData);
} catch (CertificateException ce) {
LOGGER.log(Level.WARNING, "Exceptiong while extracting document signing certificate", ce);
}
this.securityInfos = getSecurityInfos(signedData);
this.encryptedDigest = SignedDataUtil.getEncryptedDigest(signedData);
}
protected void writeContent(OutputStream outputStream) throws IOException {
try {
ContentInfo contentInfo = toContentInfo(CONTENT_TYPE_OID, securityInfos);
SignedData signedData = SignedDataUtil.createSignedData(digestAlgorithm, digestEncryptionAlgorithm, CONTENT_TYPE_OID, contentInfo, encryptedDigest, certificate);
SignedDataUtil.writeData(signedData, outputStream);
} catch (CertificateException ce) {
throw new IOException("Certificate exception during SignedData creation", ce);
} catch (NoSuchAlgorithmException nsae) {
throw new IOException("Unsupported algorithm", nsae);
}
}
/**
* Gets the DER encoded file.
*
* @return the encoded file
*/
public byte[] getEncoded() {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
writeContent(outputStream);
outputStream.close();
return outputStream.toByteArray();
} catch (IOException ioe) {
return null;
}
}
/**
* Gets the security infos as an unordered collection.
*
* @return security infos
*/
public Collection getSecurityInfos() {
return securityInfos;
}
/**
* Gets the PACE infos embedded in this card access file.
* If no infos are present, an empty list is returned.
*
* @return a list of PACE infos
*/
public Collection getPACEInfos() {
List paceInfos = new ArrayList(securityInfos.size());
for (SecurityInfo securityInfo: securityInfos) {
if (securityInfo instanceof PACEInfo) {
paceInfos.add((PACEInfo)securityInfo);
}
}
return paceInfos;
}
/**
* Gets the CA public key infos embedded in this card access file.
* If no infos are present, an empty list is returned.
*
* @return a list of CA public key infos
*/
public Collection getChipAuthenticationInfos() {
List chipAuthenticationInfos = new ArrayList(securityInfos.size());
for (SecurityInfo securityInfo: securityInfos) {
if (securityInfo instanceof ChipAuthenticationInfo) {
chipAuthenticationInfos.add((ChipAuthenticationInfo)securityInfo);
}
}
return chipAuthenticationInfos;
}
/**
* Gets the CA public key infos embedded in this card access file.
* If no infos are present, an empty list is returned.
*
* @return a list of CA public key infos
*/
public Collection getChipAuthenticationPublicKeyInfos() {
List chipAuthenticationPublicKeyInfos = new ArrayList(securityInfos.size());
for (SecurityInfo securityInfo: securityInfos) {
if (securityInfo instanceof ChipAuthenticationPublicKeyInfo) {
chipAuthenticationPublicKeyInfos.add((ChipAuthenticationPublicKeyInfo)securityInfo);
}
}
return chipAuthenticationPublicKeyInfos;
}
/**
* Gets the signature algorithm object identifier.
*
* @return signature algorithm OID
*/
public String toString() {
return "CardSecurityFile [" + securityInfos.toString() + "]";
}
/**
* Tests equality with respect to another object.
*
* @param otherObj another object
*
* @return whether this object equals the other object
*/
public boolean equals(Object otherObj) {
if (otherObj == null) {
return false;
}
if (!(otherObj.getClass().equals(this.getClass()))) {
return false;
}
CardSecurityFile other = (CardSecurityFile)otherObj;
if (securityInfos == null) {
return other.securityInfos == null;
}
if (other.securityInfos == null) {
return securityInfos == null;
}
return securityInfos.equals(other.securityInfos);
}
/**
* Gets a hash code of this object.
*
* @return the hash code
*/
public int hashCode() {
return 3 * securityInfos.hashCode() + 63;
}
/* FIXME: rewrite (using writeObject instead of getDERObject) to remove interface dependency on BC. */
private static ContentInfo toContentInfo(String contentTypeOID, Collection securityInfos) {
try {
ASN1EncodableVector vector = new ASN1EncodableVector();
for (SecurityInfo si : securityInfos) {
vector.add(si.getDERObject());
}
ASN1Set derSet = new DLSet(vector);
return new ContentInfo(new ASN1ObjectIdentifier(contentTypeOID), new DEROctetString(derSet));
} catch (IOException ioe) {
LOGGER.log(Level.WARNING, "Error creating signedData", ioe);
throw new IllegalArgumentException("Error DER encoding the security infos");
}
}
private static Set getSecurityInfos(SignedData signedData) throws IOException {
ASN1Primitive encapsulatedContent = SignedDataUtil.getContent(signedData);
if (!(encapsulatedContent instanceof ASN1Set)) {
throw new IOException("Was expecting an ASN1Set, found " + encapsulatedContent.getClass());
}
ASN1Set set = (ASN1Set)encapsulatedContent;
Set securityInfos = new HashSet();
for (int i = 0; i < set.size(); i++) {
ASN1Primitive object = set.getObjectAt(i).toASN1Primitive();
try {
SecurityInfo securityInfo = SecurityInfo.getInstance(object);
if (securityInfo == null) {
LOGGER.log(Level.WARNING, "Could not parse, skipping security info");
continue;
}
securityInfos.add(securityInfo);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Exception while parsing, skipping security info", e);
}
}
return securityInfos;
}
}