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 - 2018 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 1827 2019-11-21 11:31:13Z 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.GeneralSecurityException;
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.Arrays;
import java.util.Collection;
import java.util.Collections;
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).
*
* @author The JMRTD team ([email protected])
*
* @version $Revision: 1827 $
*
* @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);
}
/**
* Returns the digest algorithm.
*
* @return the digest algorithm
*/
public String getDigestAlgorithm() {
return digestAlgorithm;
}
/**
* Returns the signature algorithm.
*
* @return the signature algorithm
*/
public String getDigestEncryptionAlgorithm() {
return digestEncryptionAlgorithm;
}
/**
* Returns the encrypted digest (signature bytes).
*
* @return the encrypted digest
*/
public byte[] getEncryptedDigest() {
return encryptedDigest == null ? null : Arrays.copyOf(encryptedDigest, encryptedDigest.length);
}
/**
* Reads the contents of this file from a stream.
*
* @param inputStream the stream to read from
*
* @throws IOException on error reading from the stream
*/
protected void readContent(InputStream inputStream) throws IOException {
SignedData signedData = SignedDataUtil.readSignedData(inputStream);
this.digestAlgorithm = SignedDataUtil.getSignerInfoDigestAlgorithm(signedData);
this.digestEncryptionAlgorithm = SignedDataUtil.getDigestEncryptionAlgorithm(signedData);
List certificates = SignedDataUtil.getCertificates(signedData);
this.certificate = certificates == null || certificates.isEmpty() ? null : certificates.get(certificates.size() - 1);
this.securityInfos = getSecurityInfos(signedData);
this.encryptedDigest = SignedDataUtil.getEncryptedDigest(signedData);
}
/**
* Writes the contents of this file to a stream.
*
* @param outputStream the stream to write to
*
* @throws IOException on error writing to the stream
*/
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);
} catch (GeneralSecurityException gse) {
throw new IOException("General security exception", gse);
}
}
/**
* Returns a DER encoded of this file.
*
* @return the encoded file
*/
public byte[] getEncoded() {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
writeContent(byteArrayOutputStream);
byteArrayOutputStream.flush();
return byteArrayOutputStream.toByteArray();
} catch (IOException ioe) {
LOGGER.log(Level.WARNING, "Exception while encoding", ioe);
return null;
} finally {
try {
byteArrayOutputStream.close();
} catch (IOException ioe) {
LOGGER.log(Level.FINE, "Error closing stream");
}
}
}
/**
* Returns the security infos as an unordered collection.
*
* @return security infos
*/
public Collection getSecurityInfos() {
return Collections.unmodifiableCollection(securityInfos);
}
/**
* Returns 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
*
* @deprecated Use filter utility functions in {@code SignedDataUtil} instead.
*/
@Deprecated
public Collection getPACEInfos() {
List paceInfos = new ArrayList(securityInfos.size());
for (SecurityInfo securityInfo: securityInfos) {
if (securityInfo instanceof PACEInfo) {
paceInfos.add((PACEInfo)securityInfo);
}
}
return paceInfos;
}
/**
* Returns 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
*
* @deprecated Use filter utility functions in {@code SignedDataUtil} instead.
*/
@Deprecated
public Collection getChipAuthenticationInfos() {
List chipAuthenticationInfos = new ArrayList(securityInfos.size());
for (SecurityInfo securityInfo: securityInfos) {
if (securityInfo instanceof ChipAuthenticationInfo) {
chipAuthenticationInfos.add((ChipAuthenticationInfo)securityInfo);
}
}
return chipAuthenticationInfos;
}
/**
* Returns 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
*
* @deprecated Use filter utility functions in {@code SignedDataUtil} instead.
*/
@Deprecated
public Collection getChipAuthenticationPublicKeyInfos() {
List chipAuthenticationPublicKeyInfos = new ArrayList(securityInfos.size());
for (SecurityInfo securityInfo: securityInfos) {
if (securityInfo instanceof ChipAuthenticationPublicKeyInfo) {
chipAuthenticationPublicKeyInfos.add((ChipAuthenticationPublicKeyInfo)securityInfo);
}
}
return chipAuthenticationPublicKeyInfos;
}
/**
* Returns the signature algorithm object identifier.
*
* @return signature algorithm OID
*/
@Override
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
*/
@Override
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);
}
/**
* Returns a hash code of this object.
*
* @return the hash code
*/
@Override
public int hashCode() {
return 3 * securityInfos.hashCode() + 63;
}
/* FIXME: rewrite (using writeObject instead of getDERObject) to remove interface dependency on BC. */
/**
* Computes content info from the given list of security infos.
*
* @param contentTypeOID the object identifier to use
* @param securityInfos the list of security infos
*
* @return the content info
*/
private static ContentInfo toContentInfo(String contentTypeOID, Collection securityInfos) {
try {
ASN1EncodableVector vector = new ASN1EncodableVector();
for (SecurityInfo securityInfo: securityInfos) {
vector.add(securityInfo.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");
}
}
/**
* Attempts to interpret the contents of the given signed data structure as a collection of security infos.
* If the data does not contain any security infos, the empty set is returned.
*
* @param signedData the signed data structure to parse
*
* @return the set of security infos inside the signed data structure
*
* @throws IOException on parse error
*/
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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy