
org.jmrtd.lds.SODFile Maven / Gradle / Ivy
/*
* JMRTD - A Java API for accessing machine readable travel documents.
*
* Copyright (C) 2006 - 2015 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 1573 2015-03-04 16:00:18Z martijno $
*/
package org.jmrtd.lds;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
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.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.BERTaggedObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.DLSet;
import org.bouncycastle.asn1.DLTaggedObject;
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.icao.LDSSecurityObject;
import org.bouncycastle.asn1.icao.LDSVersionInfo;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.jce.provider.X509CertificateObject;
import org.jmrtd.JMRTDSecurityProvider;
/**
* File structure for the EF_SOD file (the Document Security Object).
* Based on Appendix 3 of Doc 9303 Part 1 Vol 2.
*
* Basically the Document Security Object is a SignedData type as specified in
* RFC 3369.
*
* @author Wojciech Mostowski ([email protected])
* @author Martijn Oostdijk ([email protected])
*
* @version $Revision: 1573 $
*/
public class SODFile extends DataGroup { /* FIXME: strictly speaking this is not a DataGroup, consider changing the name of the DataGroup class. */
private static final long serialVersionUID = -1081347374739311111L;
// private static final String SHA1_HASH_ALG_OID = "1.3.14.3.2.26";
// private static final String SHA1_WITH_RSA_ENC_OID = "1.2.840.113549.1.1.5";
// private static final String SHA256_HASH_ALG_OID = "2.16.840.1.101.3.4.2.1";
// private static final String E_CONTENT_TYPE_OID = "1.2.528.1.1006.1.20.1";
/**
* OID to indicate content-type in encapContentInfo.
*
*
* id-icao-ldsSecurityObject OBJECT IDENTIFIER ::=
* {joint-iso-itu-t(2) international-organizations(23) icao(136) mrtd(1) security(1) ldsSecurityObject(1)}
*
*/
private static final String ICAO_LDS_SOD_OID = "2.23.136.1.1.1";
/**
* This TC_SOD_IOD is apparently used in
* "PKI for Machine Readable Travel Documents Offering ICC Read-Only Access Version - 1.1, Annex C".
* Seen in live French and Belgian MRTDs.
*
*
* id-icao-ldsSecurityObjectid OBJECT IDENTIFIER ::=
* {iso(1) identified-organization(3) icao(27) atn-end-system-air(1) security(1) ldsSecurityObject(1)}
*
*/
private static final String ICAO_LDS_SOD_ALT_OID = "1.3.27.1.1.1";
/**
* This is used in some test MRTDs.
* Appears to have been included in a "worked example" somewhere and perhaps used in live documents.
*
*
* id-sdu-ldsSecurityObjectid OBJECT IDENTIFIER :=
* {iso(1) member-body(2) nl(528) nederlandse-organisatie(1) enschede-sdu(1006) 1 20 1}
*
*/
private static final String SDU_LDS_SOD_OID = "1.2.528.1.1006.1.20.1";
private static final String
RFC_3369_SIGNED_DATA_OID = "1.2.840.113549.1.7.2", /* id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 } */
RFC_3369_CONTENT_TYPE_OID = "1.2.840.113549.1.9.3",
RFC_3369_MESSAGE_DIGEST_OID = "1.2.840.113549.1.9.4",
PKCS1_RSA_OID = "1.2.840.113549.1.1.1",
PKCS1_MD2_WITH_RSA_OID = "1.2.840.113549.1.1.2",
PKCS1_MD4_WITH_RSA_OID = "1.2.840.113549.1.1.3",
PKCS1_MD5_WITH_RSA_OID = "1.2.840.113549.1.1.4",
PKCS1_SHA1_WITH_RSA_OID = "1.2.840.113549.1.1.5",
// PKCS1_RSAOAEP_ENC_SET = "1.2.840.113549.1.1.6", // other identifier: ripemd160WithRSAEncryption
// PKCS1_RSAES_OAEP = "1.2.840.113549.1.1.7",
PKCS1_SHA256_WITH_RSA_AND_MGF1 = "1.2.840.113549.1.1.8",
PKCS1_RSASSA_PSS_OID = "1.2.840.113549.1.1.10",
PKCS1_SHA256_WITH_RSA_OID = "1.2.840.113549.1.1.11",
PKCS1_SHA384_WITH_RSA_OID = "1.2.840.113549.1.1.12",
PKCS1_SHA512_WITH_RSA_OID = "1.2.840.113549.1.1.13",
PKCS1_SHA224_WITH_RSA_OID = "1.2.840.113549.1.1.14",
X9_SHA1_WITH_ECDSA_OID = "1.2.840.10045.4.1",
X9_SHA224_WITH_ECDSA_OID = "1.2.840.10045.4.3.1",
X9_SHA256_WITH_ECDSA_OID = "1.2.840.10045.4.3.2",
IEEE_P1363_SHA1_OID = "1.3.14.3.2.26";
private static final Provider BC_PROVIDER = JMRTDSecurityProvider.getBouncyCastleProvider();
private static final Logger LOGGER = Logger.getLogger("org.jmrtd");
private SignedData signedData;
/**
* Constructs a Security Object data structure.
*
* @param digestAlgorithm a digest algorithm, such as "SHA-1" or "SHA-256"
* @param digestEncryptionAlgorithm a digest encryption algorithm, such as "SHA256withRSA"
* @param dataGroupHashes maps datagroup numbers (1 to 16) to hashes of the data groups
* @param encryptedDigest ???
* @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 dataGroupHashes,
byte[] encryptedDigest,
X509Certificate docSigningCertificate) throws NoSuchAlgorithmException, CertificateException {
super(EF_SOD_TAG);
try {
signedData = createSignedData(digestAlgorithm,
digestEncryptionAlgorithm,
dataGroupHashes,
encryptedDigest,
docSigningCertificate);
} catch (IOException ioe) {
LOGGER.severe("Error creating signedData: " + ioe.getMessage());
throw new IllegalArgumentException(ioe.getMessage());
}
}
/**
* Constructs a Security Object data structure using a specified signature provider.
*
* @param digestAlgorithm a digest algorithm, such as "SHA-1" or "SHA-256"
* @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 dataGroupHashes,
PrivateKey privateKey,
X509Certificate docSigningCertificate, String provider) throws NoSuchAlgorithmException, CertificateException {
super(EF_SOD_TAG);
try {
signedData = createSignedData(digestAlgorithm,
digestEncryptionAlgorithm,
dataGroupHashes,
privateKey,
docSigningCertificate, provider);
} catch (IOException ioe) {
LOGGER.severe("Error creating signedData: " + ioe.getMessage());
throw new IllegalArgumentException(ioe.getMessage());
}
}
/**
* Constructs a Security Object data structure using a specified signature provider.
*
* @param digestAlgorithm a digest algorithm, such as "SHA-1" or "SHA-256"
* @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 LDS version
* @param unicodeVersion Unicode version
*
* @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 dataGroupHashes,
PrivateKey privateKey,
X509Certificate docSigningCertificate, String provider,
String ldsVersion, String unicodeVersion) throws NoSuchAlgorithmException, CertificateException {
super(EF_SOD_TAG);
try {
signedData = createSignedData(digestAlgorithm,
digestEncryptionAlgorithm,
dataGroupHashes,
privateKey,
docSigningCertificate, provider, ldsVersion, unicodeVersion);
} catch (IOException ioe) {
LOGGER.severe("Error creating signedData: " + ioe.getMessage());
throw new IllegalArgumentException(ioe.getMessage());
}
}
/**
* 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 dataGroupHashes,
PrivateKey privateKey,
X509Certificate docSigningCertificate)
throws NoSuchAlgorithmException, CertificateException {
super(EF_SOD_TAG);
try {
signedData = createSignedData(digestAlgorithm,
digestEncryptionAlgorithm,
dataGroupHashes,
privateKey,
docSigningCertificate, null);
} catch (IOException ioe) {
LOGGER.severe("Error creating signedData: " + ioe.getMessage());
throw new IllegalArgumentException(ioe.getMessage());
}
}
/**
* Constructs a Security Object data structure.
*
* @param inputStream some inputstream
*
* @throws IOException if something goes wrong
*/
public SODFile(InputStream inputStream) throws IOException {
super(EF_SOD_TAG, inputStream);
}
protected void readContent(InputStream inputStream) throws IOException {
ASN1InputStream asn1in = new ASN1InputStream(inputStream);
ASN1Sequence sequence = (ASN1Sequence)asn1in.readObject();
if (sequence.size() != 2) {
throw new IOException("Was expecting a DER sequence of length 2, found a DER sequence of length " + sequence.size());
}
String contentTypeOID = ((ASN1ObjectIdentifier)sequence.getObjectAt(0)).getId();
if (!RFC_3369_SIGNED_DATA_OID.equals(contentTypeOID)) {
throw new IOException("Was expecting signed-data content type OID (" + RFC_3369_SIGNED_DATA_OID + "), found " + contentTypeOID);
}
ASN1Primitive content = null;
int tagNo = -1;
ASN1Encodable asn1Encodable = sequence.getObjectAt(1);
/*
* Most EU passports have DERTaggedObject,
* New Zealand has BERTaggedObject,
* cast problems between BER and DER since (at least) BC 1.47...
* Thanks to Nick von Dadelszen for helping out with debugging.
*/
if (asn1Encodable instanceof DERTaggedObject) {
DERTaggedObject derTaggedObject = (DERTaggedObject)asn1Encodable;
tagNo = derTaggedObject.getTagNo();
content = derTaggedObject.getObject();
} else if (asn1Encodable instanceof BERTaggedObject) {
BERTaggedObject berTaggedObject = (BERTaggedObject)asn1Encodable;
tagNo = berTaggedObject.getTagNo();
content = berTaggedObject.getObject();
} else if (asn1Encodable instanceof ASN1TaggedObject) {
DLTaggedObject dlTaggedObject = (DLTaggedObject)asn1Encodable;
tagNo = dlTaggedObject.getTagNo();
content = dlTaggedObject.getObject();
} else if (asn1Encodable instanceof ASN1TaggedObject) {
ASN1TaggedObject asn1TaggedObject = (ASN1TaggedObject)asn1Encodable;
tagNo = asn1TaggedObject.getTagNo();
content = asn1TaggedObject.getObject();
} else {
throw new IOException("Was expecting an ASN1TaggedObject, found " + asn1Encodable.getClass().getCanonicalName());
}
if (tagNo != 0) {
throw new IOException("Was expecting tag 0, found " + Integer.toHexString(tagNo));
}
if (!(content instanceof ASN1Sequence)) {
throw new IOException("Was expecting an ASN.1 sequence as content");
}
this.signedData = SignedData.getInstance((ASN1Sequence)content);
}
protected void writeContent(OutputStream out) throws IOException {
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1ObjectIdentifier(RFC_3369_SIGNED_DATA_OID));
v.add(new DERTaggedObject(0, signedData));
ASN1Sequence fileContentsObject = new DLSequence(v);
byte[] fileContentsBytes = fileContentsObject.getEncoded(ASN1Encoding.DER);
out.write(fileContentsBytes);
}
/**
* Gets the stored data group hashes.
*
* @return data group hashes indexed by data group numbers (1 to 16)
*/
public Map getDataGroupHashes() {
DataGroupHash[] hashObjects = getLDSSecurityObject(signedData).getDatagroupHash();
Map hashMap = new TreeMap(); /* HashMap... get it? :D (not funny anymore, now that it's a TreeMap.) */
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 e-content inside the signed data strucure.
*
* @return the e-content
*/
public byte[] getEContent() {
return getEContent(signedData);
}
/**
* Gets the name of the algorithm used in the data group hashes.
*
* @return an algorithm string such as "SHA-1" or "SHA-256"
*/
public String getDigestAlgorithm() {
try {
return lookupMnemonicByOID(getLDSSecurityObject(signedData).getDigestAlgorithmIdentifier().getAlgorithm().getId());
} catch (NoSuchAlgorithmException nsae) {
LOGGER.severe("Exception: " + nsae.getMessage());
return null; // throw new IllegalStateException(nsae.toString());
}
}
/**
* Gets the name of the digest algorithm used in the signature.
*
* @return an algorithm string such as "SHA-1" or "SHA-256"
*/
public String getSignerInfoDigestAlgorithm() {
try {
SignerInfo signerInfo = getSignerInfo(signedData);
String digestAlgOID = signerInfo.getDigestAlgorithm().getAlgorithm().getId();
return lookupMnemonicByOID(digestAlgOID);
} catch (NoSuchAlgorithmException nsae) {
LOGGER.severe("Exception: " + nsae.getMessage());
return null; // throw new IllegalStateException(nsae.toString());
}
}
/**
* Gets the name of the digest encryption algorithm used in the signature.
*
* @return an algorithm string such as "SHA256withRSA"
*/
public String getDigestEncryptionAlgorithm() {
try {
SignerInfo signerInfo = getSignerInfo(signedData);
String digestEncryptionAlgorithmOID = signerInfo.getDigestEncryptionAlgorithm().getAlgorithm().getId();
if (digestEncryptionAlgorithmOID == null) { return null; }
return lookupMnemonicByOID(digestEncryptionAlgorithmOID);
} catch (NoSuchAlgorithmException nsae) {
LOGGER.severe("Exception: " + nsae.getMessage());
return null; // 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 < V1.8
*
* @since LDS V1.8
*/
public String getLDSVersion() {
LDSVersionInfo ldsVersionInfo = getLDSSecurityObject(signedData).getVersionInfo();
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 < V1.8
*
* @since LDS V1.8
*/
public String getUnicodeVersion() {
LDSVersionInfo ldsVersionInfo = getLDSSecurityObject(signedData).getVersionInfo();
if (ldsVersionInfo == null) {
return null;
} else {
return ldsVersionInfo.getUnicodeVersion();
}
}
/**
* Gets the embedded document signing certificate (if present).
* Use this certificate to verify that eSignature is a valid
* signature for eContent. This certificate itself is signed
* using the country signing certificate.
*
* @return the document signing certificate
*
* @throws CertificateException when certificate not be constructed from this SOd
*/
public X509Certificate getDocSigningCertificate() throws CertificateException {
byte[] certSpec = null;
ASN1Set certs = signedData.getCertificates();
if (certs == null || certs.size() <= 0) { return null; }
if (certs.size() != 1) {
LOGGER.warning("Found " + certs.size() + " certificates");
}
X509CertificateObject certObject = null;
for (int i = 0; i < certs.size(); i++) {
org.bouncycastle.asn1.x509.Certificate certAsASN1Object = org.bouncycastle.asn1.x509.Certificate.getInstance((ASN1Sequence)certs.getObjectAt(i));
certObject = new X509CertificateObject(certAsASN1Object); // NOTE: >= BC 1.48
// certObject = new X509CertificateObject(X509CertificateStructure.getInstance(certAsASN1Object)); // NOTE: <= BC 1.47
certSpec = certObject.getEncoded();
}
/*
* NOTE: we could have just returned that X509CertificateObject here,
* but by reconstructing it using the client's default provider we hide
* the fact that we're using BC.
*/
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(certSpec));
return cert;
} catch (Exception e) {
/* NOTE: Reconstructing using preferred provider didn't work?!?! */
return certObject;
}
}
/**
* 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
*/
/* FIXME: move this out of lds package. */
public boolean checkDocSignature(Certificate docSigningCert) throws GeneralSecurityException {
byte[] eContent = getEContent();
byte[] signature = getEncryptedDigest();
String digestEncryptionAlgorithm = null;
try {
digestEncryptionAlgorithm = getDigestEncryptionAlgorithm();
} catch (Exception e) {
digestEncryptionAlgorithm = null;
}
/*
* For the cases where the signature is simply a digest (haven't seen a passport like this,
* thus this is guessing)
*/
if (digestEncryptionAlgorithm == null) {
String digestAlg = getSignerInfoDigestAlgorithm();
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance(digestAlg);
} catch (Exception e) {
digest = MessageDigest.getInstance(digestAlg, BC_PROVIDER);
}
digest.update(eContent);
byte[] digestBytes = digest.digest();
return Arrays.equals(digestBytes, signature);
}
/* For RSA_SA_PSS
* 1. the default hash is SHA1,
* 2. The hash id is not encoded in OID
* So it has to be specified "manually".
*/
if ("SSAwithRSA/PSS".equals(digestEncryptionAlgorithm)) {
String digestAlg = getSignerInfoDigestAlgorithm();
digestEncryptionAlgorithm = digestAlg.replace("-", "") + "withRSA/PSS";
}
if ("RSA".equals(digestEncryptionAlgorithm)) {
String digestJavaString = getSignerInfoDigestAlgorithm();
digestEncryptionAlgorithm = digestJavaString.replace("-", "") + "withRSA";
}
LOGGER.info("digestEncryptionAlgorithm = " + digestEncryptionAlgorithm);
Signature sig = null;
try {
sig = Signature.getInstance(digestEncryptionAlgorithm);
} catch (Exception e) {
sig = Signature.getInstance(digestEncryptionAlgorithm, BC_PROVIDER);
}
sig.initVerify(docSigningCert);
sig.update(eContent);
return sig.verify(signature);
}
/**
* Gets the issuer of the document signing certificate.
*
* @return a certificate issuer
*/
public X500Principal getIssuerX500Principal() {
try {
IssuerAndSerialNumber issuerAndSerialNumber = getIssuerAndSerialNumber();
X500Name name = issuerAndSerialNumber.getName();
X500Principal x500Principal = new X500Principal(name.getEncoded(ASN1Encoding.DER));
return x500Principal;
} catch (IOException ioe) {
LOGGER.severe("Could not get issuer: " + ioe.getMessage());
return null;
}
}
/**
* Gets the serial number of the document signing certificate.
*
* @return a certificate serial number
*/
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
*/
public String toString() {
try {
X509Certificate cert = getDocSigningCertificate();
return "SODFile " + cert.getIssuerX500Principal();
} catch (Exception e) {
return "SODFile";
}
}
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;
return Arrays.equals(getEncoded(), other.getEncoded());
}
public int hashCode() {
return 11 * Arrays.hashCode(getEncoded()) + 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((ASN1Sequence)signerInfos.getObjectAt(i));
return info;
}
return null;
}
/**
* Reads the security object (containing the hashes
* of the data groups) found in the SignedData field.
*
* @return the security object
*
* @throws IOException
*/
private static LDSSecurityObject getLDSSecurityObject(SignedData signedData) {
try {
ContentInfo encapContentInfo = signedData.getEncapContentInfo();
String contentType = encapContentInfo.getContentType().getId();
DEROctetString eContent = (DEROctetString)encapContentInfo.getContent();
if (!(ICAO_LDS_SOD_OID.equals(contentType)
|| SDU_LDS_SOD_OID.equals(contentType)
|| ICAO_LDS_SOD_ALT_OID.equals(contentType))) {
LOGGER.warning("SignedData does not appear to contain an LDS SOd. (content type is " + contentType + ", was expecting " + ICAO_LDS_SOD_OID + ")");
}
ASN1InputStream inputStream = new ASN1InputStream(new ByteArrayInputStream(eContent.getOctets()));
Object firstObject = inputStream.readObject();
if (!(firstObject instanceof ASN1Sequence)) {
throw new IllegalStateException("Expected ASN1Sequence, found " + firstObject.getClass().getSimpleName());
}
LDSSecurityObject sod = LDSSecurityObject.getInstance(firstObject);
Object nextObject = inputStream.readObject();
if (nextObject != null) {
LOGGER.warning("Ignoring 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) {
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 = null;
String digAlg = signerInfo.getDigestAlgorithm().getAlgorithm().getId();
try {
attributesBytes = signedAttributesSet.getEncoded(ASN1Encoding.DER);
/* 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((ASN1Sequence)attributes.nextElement());
ASN1ObjectIdentifier attrType = attribute.getAttrType();
if (RFC_3369_MESSAGE_DIGEST_OID.equals(attrType.getId())) {
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 signedAttributes in eContent! No such algorithm: \"" + digAlg + "\": " + nsae.getMessage());
} catch (IOException ioe) {
LOGGER.severe("Error getting signedAttributes: " + ioe.getMessage());
}
return attributesBytes;
}
}
private IssuerAndSerialNumber getIssuerAndSerialNumber() {
SignerInfo signerInfo = getSignerInfo(signedData);
SignerIdentifier signerIdentifier = signerInfo.getSID();
IssuerAndSerialNumber issuerAndSerialNumber = IssuerAndSerialNumber.getInstance(signerIdentifier.getId());
X500Name issuer = issuerAndSerialNumber.getName();
BigInteger serialNumber = issuerAndSerialNumber.getSerialNumber().getValue();
return new IssuerAndSerialNumber(issuer, serialNumber);
}
/**
* 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 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, content, encryptedDigest, docSigningCertificate).toASN1Object());
return new SignedData(digestAlgorithmsSet, contentInfo, certificates, crls, signerInfos);
}
private static SignedData createSignedData(String digestAlgorithm,
String digestEncryptionAlgorithm,
Map 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 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();
byte[] encryptedDigest = null;
try {
byte[] dataToBeSigned = createAuthenticatedAttributes(digestAlgorithm, content).getEncoded(ASN1Encoding.DER);
Signature s = null;
if (provider != null) {
s = Signature.getInstance(digestEncryptionAlgorithm, provider);
} else {
s = Signature.getInstance(digestEncryptionAlgorithm);
}
s.initSign(privateKey);
s.update(dataToBeSigned);
encryptedDigest = s.sign();
} catch (Exception e) {
LOGGER.severe("Exception: " + e.getMessage());
return null;
}
ASN1Set certificates = createSingletonSet(createCertificate(docSigningCertificate));
ASN1Set crls = null;
ASN1Set signerInfos = createSingletonSet(createSignerInfo(
digestAlgorithm, digestEncryptionAlgorithm, content,
encryptedDigest, docSigningCertificate).toASN1Object());
return new SignedData(digestAlgorithmsSet, contentInfo, certificates, crls, signerInfos);
}
private static ASN1Sequence createDigestAlgorithms(String digestAlgorithm) throws NoSuchAlgorithmException {
ASN1ObjectIdentifier algorithmIdentifier = new ASN1ObjectIdentifier(lookupOIDByMnemonic(digestAlgorithm));
// ASN1Primitive[] result = { algorithmIdentifier };
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(algorithmIdentifier);
return new DLSequence(v);
}
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 dataGroupHashes) throws NoSuchAlgorithmException, IOException {
return createContentInfo(digestAlgorithm, dataGroupHashes, null, null);
}
private static ContentInfo createContentInfo(
String digestAlgorithm,
Map 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 = AlgorithmIdentifier.getInstance(lookupOIDByMnemonic(digestAlgorithm));
LDSVersionInfo ldsVersionInfo;
LDSSecurityObject securityObject = null;
if (ldsVersion == null) {
ldsVersionInfo = null;
securityObject = new LDSSecurityObject(digestAlgorithmIdentifier, dataGroupHashesArray);
} else {
ldsVersionInfo = new LDSVersionInfo(ldsVersion, unicodeVersion);
securityObject = new LDSSecurityObject(digestAlgorithmIdentifier, dataGroupHashesArray, ldsVersionInfo);
}
return new ContentInfo(new ASN1ObjectIdentifier(ICAO_LDS_SOD_OID), new DEROctetString(securityObject));
}
private static SignerInfo createSignerInfo(String digestAlgorithm,
String digestEncryptionAlgorithm, byte[] content,
byte[] encryptedDigest, X509Certificate docSigningCertificate) throws NoSuchAlgorithmException {
/* Get the issuer name (CN, O, OU, C) from the cert and put it in a SignerIdentifier struct. */
X500Principal docSignerPrincipal = ((X509Certificate)docSigningCertificate).getIssuerX500Principal();
X500Name docSignerName = new X500Name(docSignerPrincipal.getName(X500Principal.RFC2253));
BigInteger serial = ((X509Certificate)docSigningCertificate).getSerialNumber();
SignerIdentifier sid = new SignerIdentifier(new IssuerAndSerialNumber(docSignerName, serial));
AlgorithmIdentifier digestAlgorithmObject = new AlgorithmIdentifier(lookupOIDByMnemonic(digestAlgorithm));
AlgorithmIdentifier digestEncryptionAlgorithmObject = new AlgorithmIdentifier(lookupOIDByMnemonic(digestEncryptionAlgorithm));
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 {
/* Check bug found by Paulo Assumpco. */
if ("SHA256".equals(digestAlgorithm)) { digestAlgorithm = "SHA-256"; }
MessageDigest dig = MessageDigest.getInstance(digestAlgorithm);
byte[] digestedContentBytes = dig.digest(contentBytes);
ASN1OctetString digestedContent = new DEROctetString(digestedContentBytes);
Attribute contentTypeAttribute = new Attribute(new ASN1ObjectIdentifier(RFC_3369_CONTENT_TYPE_OID), createSingletonSet(new ASN1ObjectIdentifier(ICAO_LDS_SOD_OID)));
Attribute messageDigestAttribute = new Attribute(new ASN1ObjectIdentifier(RFC_3369_MESSAGE_DIGEST_OID), createSingletonSet(digestedContent));
ASN1Object[] result = { contentTypeAttribute.toASN1Primitive(), messageDigestAttribute.toASN1Primitive() };
return new DLSet(result);
}
private static ASN1Set createSingletonSet(ASN1Object e) {
return new DLSet(new ASN1Encodable[] { e });
}
/**
* Gets the common mnemonic string (such as "SHA1", "SHA256withRSA") given an OID.
*
* @param oid an OID
*
* @throws NoSuchAlgorithmException if the provided OID is not yet supported
*/
private static String lookupMnemonicByOID(String oid) throws NoSuchAlgorithmException {
if (oid == null) { return null; }
if (oid.equals(X509ObjectIdentifiers.organization.getId())) { return "O"; }
if (oid.equals(X509ObjectIdentifiers.organizationalUnitName.getId())) { return "OU"; }
if (oid.equals(X509ObjectIdentifiers.commonName.getId())) { return "CN"; }
if (oid.equals(X509ObjectIdentifiers.countryName.getId())) { return "C"; }
if (oid.equals(X509ObjectIdentifiers.stateOrProvinceName.getId())) { return "ST"; }
if (oid.equals(X509ObjectIdentifiers.localityName.getId())) { return "L"; }
if(oid.equals(X509ObjectIdentifiers.id_SHA1.getId())) { return "SHA-1"; }
if(oid.equals(NISTObjectIdentifiers.id_sha224.getId())) { return "SHA-224"; }
if(oid.equals(NISTObjectIdentifiers.id_sha256.getId())) { return "SHA-256"; }
if(oid.equals(NISTObjectIdentifiers.id_sha384.getId())) { return "SHA-384"; }
if(oid.equals(NISTObjectIdentifiers.id_sha512.getId())) { return "SHA-512"; }
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_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 "SHA-1"; }
if (oid.equals(PKCS1_RSASSA_PSS_OID)) { return "SSAwithRSA/PSS"; }
if (oid.equals(PKCS1_SHA256_WITH_RSA_AND_MGF1)) { return "SHA256withRSAandMGF1"; }
throw new NoSuchAlgorithmException("Unknown OID " + oid);
}
private static String lookupOIDByMnemonic(String name) throws NoSuchAlgorithmException {
if (name.equals("O")) { return X509ObjectIdentifiers.organization.getId(); }
if (name.equals("OU")) { return X509ObjectIdentifiers.organizationalUnitName.getId(); }
if (name.equals("CN")) { return X509ObjectIdentifiers.commonName.getId(); }
if (name.equals("C")) { return X509ObjectIdentifiers.countryName.getId(); }
if (name.equals("ST")) { return X509ObjectIdentifiers.stateOrProvinceName.getId(); }
if (name.equals("L")) { return X509ObjectIdentifiers.localityName.getId(); }
if(name.equalsIgnoreCase("SHA-1") || name.equalsIgnoreCase("SHA1")) { return X509ObjectIdentifiers.id_SHA1.getId(); }
if(name.equalsIgnoreCase("SHA-224") || name.equalsIgnoreCase("SHA224")) { return NISTObjectIdentifiers.id_sha224.getId(); }
if(name.equalsIgnoreCase("SHA-256") || name.equalsIgnoreCase("SHA256")) { return NISTObjectIdentifiers.id_sha256.getId(); }
if(name.equalsIgnoreCase("SHA-384") || name.equalsIgnoreCase("SHA384")) { return NISTObjectIdentifiers.id_sha384.getId(); }
if(name.equalsIgnoreCase("SHA-512") || name.equalsIgnoreCase("SHA512")) { return NISTObjectIdentifiers.id_sha512.getId(); }
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("SAwithRSA/PSS")) { return PKCS1_RSASSA_PSS_OID; }
if (name.equalsIgnoreCase("SSAwithRSA/PSS")) { return PKCS1_RSASSA_PSS_OID; }
if (name.equalsIgnoreCase("RSASSA-PSS")) { return PKCS1_RSASSA_PSS_OID; }
if (name.equalsIgnoreCase("SHA256withRSAandMGF1")) { return PKCS1_SHA256_WITH_RSA_AND_MGF1; }
throw new NoSuchAlgorithmException("Unknown name " + name);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy