blob: 248a2e471da9f01d7282f3e7e2f979d0863439a0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2016 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which accompanies this distribution,
* and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors: IBM Corporation - initial API and implementation
******************************************************************************/
package org.eclipse.osgi.internal.signedcontent;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.text.*;
import java.util.*;
import javax.security.auth.x500.X500Principal;
import org.eclipse.osgi.util.NLS;
/**
* This class processes a PKCS7 file. See RFC 2315 for specifics.
*/
public class PKCS7Processor implements SignedContentConstants {
static CertificateFactory certFact;
static {
try {
certFact = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
} catch (CertificateException e) {
// TODO this is bad and will lead to NPEs
// Should we just throw a runtime exception to fail <clinit>?
}
}
private final String signer;
private final String file;
private Certificate[] certificates;
private Certificate[] tsaCertificates;
// key(object id) = value(structure)
private Map<int[], byte[]> signedAttrs;
// key(object id) = value(structure)
private Map<int[], byte[]> unsignedAttrs;
// store the signature of a signerinfo
private byte signature[];
private String digestAlgorithm;
private String signatureAlgorithm;
private Certificate signerCert;
private Date signingTime;
private static String oid2String(int oid[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < oid.length; i++) {
if (i > 0)
sb.append('.');
sb.append(oid[i]);
}
return sb.toString();
}
private static String findEncryption(int encOid[]) throws NoSuchAlgorithmException {
if (Arrays.equals(DSA_OID, encOid)) {
return "DSA"; //$NON-NLS-1$
}
if (Arrays.equals(RSA_OID, encOid)) {
return "RSA"; //$NON-NLS-1$
}
throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(encOid)); //$NON-NLS-1$
}
private static String findDigest(int digestOid[]) throws NoSuchAlgorithmException {
if (Arrays.equals(SHA1_OID, digestOid)) {
return SHA1_STR;
}
if (Arrays.equals(SHA224_OID, digestOid)) {
return SHA224_STR;
}
if (Arrays.equals(SHA256_OID, digestOid)) {
return SHA256_STR;
}
if (Arrays.equals(SHA384_OID, digestOid)) {
return SHA384_STR;
}
if (Arrays.equals(SHA512_OID, digestOid)) {
return SHA512_STR;
}
if (Arrays.equals(SHA512_224_OID, digestOid)) {
return SHA512_224_STR;
}
if (Arrays.equals(SHA512_256_OID, digestOid)) {
return SHA512_256_STR;
}
if (Arrays.equals(MD5_OID, digestOid)) {
return MD5_STR;
}
if (Arrays.equals(MD2_OID, digestOid)) {
return MD2_STR;
}
throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(digestOid)); //$NON-NLS-1$
}
public PKCS7Processor(byte pkcs7[], int pkcs7Offset, int pkcs7Length, String signer, String file) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, NoSuchProviderException {
this.signer = signer;
this.file = file;
// First grab the certificates
List<Certificate> certs = null;
BERProcessor bp = new BERProcessor(pkcs7, pkcs7Offset, pkcs7Length);
// Just do a sanity check and make sure we are actually doing a PKCS7
// stream
// PKCS7: Step into the ContentType
bp = bp.stepInto();
if (!Arrays.equals(bp.getObjId(), SIGNEDDATA_OID)) {
throw new SignatureException(NLS.bind(SignedContentMessages.PKCS7_Invalid_File, signer, file));
}
// PKCS7: Process the SignedData structure
bp.stepOver(); // (**wrong comments**) skip over the oid
bp = bp.stepInto(); // go into the Signed data
bp = bp.stepInto(); // It is a structure;
bp.stepOver(); // Yeah, yeah version = 1
bp.stepOver(); // We'll see the digest stuff again; digestAlgorithms
// process the encapContentInfo structure
processEncapContentInfo(bp);
bp.stepOver();
// PKCS7: check if the class tag is 0
if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 0) {
// process the certificate elements inside the signeddata strcuture
certs = processCertificates(bp);
}
if (certs == null || certs.size() < 1)
throw new SignatureException("There are no certificates in the .RSA/.DSA file!"); //$NON-NLS-1$
// Okay, here are our certificates.
bp.stepOver();
if (bp.classOfTag == BERProcessor.UNIVERSAL_TAGCLASS && bp.tag == 1) {
bp.stepOver(); // Don't use the CRLs if present
}
processSignerInfos(bp, certs);
// construct the cert path
certs = constructCertPath(certs, signerCert);
// initialize the certificates
certificates = certs.toArray(new Certificate[certs.size()]);
verifyCerts();
// if this pkcs7process is tsa asn.1 block, the signingTime should already be set
if (signingTime == null)
signingTime = PKCS7DateParser.parseDate(this, signer, file);
}
private void processEncapContentInfo(BERProcessor bp) throws SignatureException {
// check immediately if TSTInfo is there
BERProcessor encapContentBERS = bp.stepInto();
if (Arrays.equals(encapContentBERS.getObjId(), TIMESTAMP_TST_OID)) {
// eContent
encapContentBERS.stepOver();
BERProcessor encapContentBERS1 = encapContentBERS.stepInto();
// obtain eContent octet structure
byte bytesman[] = encapContentBERS1.getBytes();
BERProcessor eContentStructure = new BERProcessor(bytesman, 0, bytesman.length);
// pointing at 'version Integer' now
BERProcessor eContentBER = eContentStructure.stepInto();
int tsaVersion = eContentBER.getIntValue().intValue();
if (tsaVersion != 1)
throw new SignatureException("Not a version 1 time-stamp token"); //$NON-NLS-1$
// policty : TSAPolicyId
eContentBER.stepOver();
// messageImprint : MessageImprint
eContentBER.stepOver();
// serialNumber : INTEGER
eContentBER.stepOver();
// genTime : GeneralizedTime
eContentBER.stepOver();
// check time ends w/ 'Z'
String dateString = new String(eContentBER.getBytes(), SignedContentConstants.UTF8);
if (!dateString.endsWith("Z")) //$NON-NLS-1$
throw new SignatureException("Wrong dateformat used in time-stamp token"); //$NON-NLS-1$
// create the appropriate date time string format
// date format could be yyyyMMddHHmmss[.s...]Z or yyyyMMddHHmmssZ
int dotIndex = dateString.indexOf('.');
StringBuffer dateFormatSB = new StringBuffer("yyyyMMddHHmmss"); //$NON-NLS-1$
if (dotIndex != -1) {
// yyyyMMddHHmmss[.s...]Z, find out number of s in the bracket
int noS = dateString.indexOf('Z') - 1 - dotIndex;
dateFormatSB.append('.');
// append s
for (int i = 0; i < noS; i++) {
dateFormatSB.append('s');
}
}
dateFormatSB.append("'Z'"); //$NON-NLS-1$
try {
// if the current locale is th_TH, or ja_JP_JP, then our dateFormat object will end up with
// a calendar such as Buddhist or Japanese Imperial Calendar, and the signing time will be
// incorrect ... so always use English as the locale for parsing the time, resulting in a
// Gregorian calendar
DateFormat dateFormt = new SimpleDateFormat(dateFormatSB.toString(), Locale.ENGLISH);
dateFormt.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$
signingTime = dateFormt.parse(dateString);
} catch (ParseException e) {
throw new SignatureException(SignedContentMessages.PKCS7_Parse_Signing_Time, e);
}
}
}
private List<Certificate> constructCertPath(List<Certificate> certs, Certificate targetCert) {
List<Certificate> certsList = new ArrayList<>();
certsList.add(targetCert);
X509Certificate currentCert = (X509Certificate) targetCert;
int numIteration = certs.size();
int i = 0;
while (i < numIteration) {
X500Principal subject = currentCert.getSubjectX500Principal();
X500Principal issuer = currentCert.getIssuerX500Principal();
if (subject.equals(issuer)) {
// the cert path has been constructed
break;
}
currentCert = null;
Iterator<Certificate> itr = certs.iterator();
while (itr.hasNext()) {
X509Certificate tempCert = (X509Certificate) itr.next();
if (tempCert.getSubjectX500Principal().equals(issuer)) {
certsList.add(tempCert);
currentCert = tempCert;
}
}
i++;
}
return certsList;
}
public void verifyCerts() throws InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
if (certificates == null || certificates.length == 0) {
throw new CertificateException("There are no certificates in the signature block file!"); //$NON-NLS-1$
}
int len = certificates.length;
// check the certs validity and signatures
for (int i = 0; i < len; i++) {
X509Certificate currentX509Cert = (X509Certificate) certificates[i];
if (i == len - 1) {
if (currentX509Cert.getSubjectDN().equals(currentX509Cert.getIssuerDN()))
currentX509Cert.verify(currentX509Cert.getPublicKey());
} else {
X509Certificate nextX509Cert = (X509Certificate) certificates[i + 1];
currentX509Cert.verify(nextX509Cert.getPublicKey());
}
}
}
private Certificate processSignerInfos(BERProcessor bp, List<Certificate> certs) throws CertificateException, NoSuchAlgorithmException, SignatureException {
// We assume there is only one SingerInfo element
// PKCS7: SignerINFOS processing
bp = bp.stepInto(); // Step into the set of signerinfos
bp = bp.stepInto(); // Step into the signerinfo sequence
// make sure the version is 1
BigInteger signerInfoVersion = bp.getIntValue();
if (signerInfoVersion.intValue() != 1) {
throw new CertificateException(SignedContentMessages.PKCS7_SignerInfo_Version_Not_Supported);
}
// PKCS7: version CMSVersion
bp.stepOver(); // Skip the version
// PKCS7: sid [SignerIdentifier : issuerAndSerialNumber or subjectKeyIdentifer]
BERProcessor issuerAndSN = bp.stepInto();
X500Principal signerIssuer = new X500Principal(new ByteArrayInputStream(issuerAndSN.buffer, issuerAndSN.offset, issuerAndSN.endOffset - issuerAndSN.offset));
issuerAndSN.stepOver();
BigInteger sn = issuerAndSN.getIntValue();
// initilize the newSignerCert to the issuer cert of leaf cert
Certificate newSignerCert = null;
Iterator<Certificate> itr = certs.iterator();
// PKCS7: compuare the issuers in the issuerAndSN BER equals to the issuers in Certs generated at the beginning of this method
// it seems like there is no neeed, cause both ways use the same set of bytes
while (itr.hasNext()) {
X509Certificate cert = (X509Certificate) itr.next();
if (cert.getIssuerX500Principal().equals(signerIssuer) && cert.getSerialNumber().equals(sn)) {
newSignerCert = cert;
break;
}
}
if (newSignerCert == null)
throw new CertificateException("Signer certificate not in pkcs7block"); //$NON-NLS-1$
// set the signer cert
signerCert = newSignerCert;
// PKCS7: skip over the sid [SignerIdentifier : issuerAndSerialNumber or subjectKeyIdentifer]
bp.stepOver(); // skip the issuer name and serial number
// PKCS7: digestAlgorithm DigestAlgorithmIdentifier
BERProcessor digestAlg = bp.stepInto();
digestAlgorithm = findDigest(digestAlg.getObjId());
// PKCS7: check if the next one if context class for signedAttrs
bp.stepOver(); // skip the digest alg
// process the signed attributes if there is any
processSignedAttributes(bp);
// PKCS7: signatureAlgorithm for this SignerInfo
BERProcessor encryptionAlg = bp.stepInto();
signatureAlgorithm = findEncryption(encryptionAlg.getObjId());
bp.stepOver(); // skip the encryption alg
// PKCS7: signature
signature = bp.getBytes();
// PKCS7: Step into the unsignedAttrs,
bp.stepOver();
// process the unsigned attributes if there is any
processUnsignedAttributes(bp);
return newSignerCert;
}
private void processUnsignedAttributes(BERProcessor bp) throws SignatureException {
if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 1) {
// there are some unsignedAttrs are found!!
unsignedAttrs = new HashMap<>();
// step into a set of unsigned attributes, I believe, when steps
// into here, the 'poiter' is pointing to the first element
BERProcessor unsignedAttrsBERS = bp.stepInto();
do {
// process the unsignedAttrsBER by getting the attr type first,
// then the strcuture for the type
BERProcessor unsignedAttrBER = unsignedAttrsBERS.stepInto();
// check if it is timestamp attribute type
int[] objID = unsignedAttrBER.getObjId();
// if(Arrays.equals(TIMESTAMP_OID, objID)) {
// System.out.println("This is a timestamp type, to continue");
// }
// get the structure for the attribute type
unsignedAttrBER.stepOver();
byte[] structure = unsignedAttrBER.getBytes();
unsignedAttrs.put(objID, structure);
unsignedAttrsBERS.stepOver();
} while (!unsignedAttrsBERS.endOfSequence());
}
}
private void processSignedAttributes(BERProcessor bp) throws SignatureException {
if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS) {
// process the signed attributes
signedAttrs = new HashMap<>();
BERProcessor signedAttrsBERS = bp.stepInto();
do {
BERProcessor signedAttrBER = signedAttrsBERS.stepInto();
int[] signedAttrObjID = signedAttrBER.getObjId();
// step over to the attribute value
signedAttrBER.stepOver();
byte[] signedAttrStructure = signedAttrBER.getBytes();
signedAttrs.put(signedAttrObjID, signedAttrStructure);
signedAttrsBERS.stepOver();
} while (!signedAttrsBERS.endOfSequence());
bp.stepOver();
}
}
public Certificate[] getCertificates() {
return certificates == null ? new Certificate[0] : certificates;
}
public void verifySFSignature(byte data[], int dataOffset, int dataLength) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
Signature sig = Signature.getInstance(digestAlgorithm + "with" + signatureAlgorithm); //$NON-NLS-1$
sig.initVerify(signerCert.getPublicKey());
sig.update(data, dataOffset, dataLength);
if (!sig.verify(signature)) {
throw new SignatureException(NLS.bind(SignedContentMessages.Signature_Not_Verify, signer, file));
}
}
/**
* Return a map of signed attributes, the key(objid) = value(PKCSBlock in bytes for the key)
*
* @return map if there is any signed attributes, null otherwise
*/
public Map<int[], byte[]> getUnsignedAttrs() {
return unsignedAttrs;
}
/**
* Return a map of signed attributes, the key(objid) = value(PKCSBlock in bytes for the key)
*
* @return map if there is any signed attributes, null otherwise
*/
public Map<int[], byte[]> getSignedAttrs() {
return signedAttrs;
}
/**
*
* @param bp
* @return a List of certificates from target cert to root cert in order
*
* @throws CertificateException
* @throws SignatureException
*/
private List<Certificate> processCertificates(BERProcessor bp) throws CertificateException, SignatureException {
List<Certificate> rtvList = new ArrayList<>(3);
// Step into the first certificate-element
BERProcessor certsBERS = bp.stepInto();
do {
X509Certificate x509Cert = (X509Certificate) certFact.generateCertificate(new ByteArrayInputStream(certsBERS.buffer, certsBERS.offset, certsBERS.endOffset - certsBERS.offset));
if (x509Cert != null) {
rtvList.add(x509Cert);
}
// go to the next cert element
certsBERS.stepOver();
} while (!certsBERS.endOfSequence());
// Collections.reverse(rtvList);
return rtvList;
}
public Date getSigningTime() {
return signingTime;
}
void setTSACertificates(Certificate[] tsaCertificates) {
this.tsaCertificates = tsaCertificates;
}
public Certificate[] getTSACertificates() {
return (tsaCertificates == null) ? new Certificate[0] : tsaCertificates;
}
}