| /******************************************************************************* |
| * Copyright (c) 2006, 2019 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.nio.charset.StandardCharsets; |
| import java.security.InvalidKeyException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.Signature; |
| import java.security.SignatureException; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.X509Certificate; |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.TimeZone; |
| 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[]) { |
| StringBuilder sb = new StringBuilder(); |
| 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(), StandardCharsets.UTF_8); |
| 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('.'); |
| StringBuilder dateFormatSB = new StringBuilder("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; |
| } |
| |
| } |