Home | History | Annotate | Download | only in utils
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 /**
     19 * @author Boris Kuznetsov
     20 * @version $Revision$
     21 */
     22 package org.apache.harmony.security.utils;
     23 
     24 import java.io.ByteArrayInputStream;
     25 import java.io.IOException;
     26 import java.io.InputStream;
     27 import java.math.BigInteger;
     28 import java.security.GeneralSecurityException;
     29 import java.security.MessageDigest;
     30 import java.security.NoSuchAlgorithmException;
     31 import java.security.Principal;
     32 import java.security.Signature;
     33 import java.security.cert.Certificate;
     34 import java.security.cert.CertificateEncodingException;
     35 import java.security.cert.CertificateFactory;
     36 import java.security.cert.X509Certificate;
     37 import java.util.ArrayList;
     38 import java.util.Arrays;
     39 import java.util.Collection;
     40 import java.util.List;
     41 import javax.security.auth.x500.X500Principal;
     42 
     43 import org.apache.harmony.security.asn1.ASN1OctetString;
     44 import org.apache.harmony.security.asn1.BerInputStream;
     45 import org.apache.harmony.security.pkcs7.ContentInfo;
     46 import org.apache.harmony.security.pkcs7.SignedData;
     47 import org.apache.harmony.security.pkcs7.SignerInfo;
     48 import org.apache.harmony.security.x501.AttributeTypeAndValue;
     49 
     50 public class JarUtils {
     51 
     52     // as defined in PKCS #9: Selected Attribute Types:
     53     // http://www.ietf.org/rfc/rfc2985.txt
     54     private static final int[] MESSAGE_DIGEST_OID =
     55         new int[] {1, 2, 840, 113549, 1, 9, 4};
     56 
     57     /**
     58      * This method handle all the work with  PKCS7, ASN1 encoding, signature verifying,
     59      * and certification path building.
     60      * See also PKCS #7: Cryptographic Message Syntax Standard:
     61      * http://www.ietf.org/rfc/rfc2315.txt
     62      * @param signature - the input stream of signature file to be verified
     63      * @param signatureBlock - the input stream of corresponding signature block file
     64      * @return array of certificates used to verify the signature file
     65      * @throws IOException - if some errors occurs during reading from the stream
     66      * @throws GeneralSecurityException - if signature verification process fails
     67      */
     68     public static Certificate[] verifySignature(InputStream signature, InputStream
     69             signatureBlock) throws IOException, GeneralSecurityException {
     70 
     71         BerInputStream bis = new BerInputStream(signatureBlock);
     72         ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
     73         SignedData signedData = info.getSignedData();
     74         if (signedData == null) {
     75             throw new IOException("No SignedData found");
     76         }
     77         Collection<org.apache.harmony.security.x509.Certificate> encCerts
     78                 = signedData.getCertificates();
     79         if (encCerts.isEmpty()) {
     80             return null;
     81         }
     82         X509Certificate[] certs = new X509Certificate[encCerts.size()];
     83         CertificateFactory cf = CertificateFactory.getInstance("X.509");
     84         int i = 0;
     85         for (org.apache.harmony.security.x509.Certificate encCert : encCerts) {
     86             final byte[] encoded = encCert.getEncoded();
     87             final InputStream is = new ByteArrayInputStream(encoded);
     88             certs[i++] = new VerbatimX509Certificate((X509Certificate) cf.generateCertificate(is),
     89                     encoded);
     90         }
     91 
     92         List<SignerInfo> sigInfos = signedData.getSignerInfos();
     93         SignerInfo sigInfo;
     94         if (!sigInfos.isEmpty()) {
     95             sigInfo = sigInfos.get(0);
     96         } else {
     97             return null;
     98         }
     99 
    100         // Issuer
    101         X500Principal issuer = sigInfo.getIssuer();
    102 
    103         // Certificate serial number
    104         BigInteger snum = sigInfo.getSerialNumber();
    105 
    106         // Locate the certificate
    107         int issuerSertIndex = 0;
    108         for (i = 0; i < certs.length; i++) {
    109             if (issuer.equals(certs[i].getIssuerDN()) &&
    110                     snum.equals(certs[i].getSerialNumber())) {
    111                 issuerSertIndex = i;
    112                 break;
    113             }
    114         }
    115         if (i == certs.length) { // No issuer certificate found
    116             return null;
    117         }
    118 
    119         if (certs[issuerSertIndex].hasUnsupportedCriticalExtension()) {
    120             throw new SecurityException("Can not recognize a critical extension");
    121         }
    122 
    123         // Get Signature instance
    124         final String daOid = sigInfo.getDigestAlgorithm();
    125         final String daName = sigInfo.getDigestAlgorithmName();
    126         final String deaOid = sigInfo.getDigestEncryptionAlgorithm();
    127         final String deaName = sigInfo.getDigestEncryptionAlgorithmName();
    128 
    129         String alg = null;
    130         Signature sig = null;
    131 
    132         if (daOid != null && deaOid != null) {
    133             alg = daOid + "with" + deaOid;
    134             try {
    135                 sig = Signature.getInstance(alg);
    136             } catch (NoSuchAlgorithmException e) {
    137             }
    138 
    139             // Try to convert to names instead of OID.
    140             if (sig == null && daName != null && deaName != null) {
    141                 alg = daName + "with" + deaName;
    142                 try {
    143                     sig = Signature.getInstance(alg);
    144                 } catch (NoSuchAlgorithmException e) {
    145                 }
    146             }
    147         }
    148 
    149         if (sig == null && deaOid != null) {
    150             alg = deaOid;
    151             try {
    152                 sig = Signature.getInstance(alg);
    153             } catch (NoSuchAlgorithmException e) {
    154             }
    155 
    156             if (sig == null) {
    157                 alg = deaName;
    158                 try {
    159                     sig = Signature.getInstance(alg);
    160                 } catch (NoSuchAlgorithmException e) {
    161                 }
    162             }
    163         }
    164 
    165         // We couldn't find a valid Signature type.
    166         if (sig == null) {
    167             return null;
    168         }
    169 
    170         sig.initVerify(certs[issuerSertIndex]);
    171 
    172         // If the authenticatedAttributes field of SignerInfo contains more than zero attributes,
    173         // compute the message digest on the ASN.1 DER encoding of the Attributes value.
    174         // Otherwise, compute the message digest on the data.
    175         List<AttributeTypeAndValue> atr = sigInfo.getAuthenticatedAttributes();
    176 
    177         byte[] sfBytes = new byte[signature.available()];
    178         signature.read(sfBytes);
    179 
    180         if (atr == null) {
    181             sig.update(sfBytes);
    182         } else {
    183             sig.update(sigInfo.getEncodedAuthenticatedAttributes());
    184 
    185             // If the authenticatedAttributes field contains the message-digest attribute,
    186             // verify that it equals the computed digest of the signature file
    187             byte[] existingDigest = null;
    188             for (AttributeTypeAndValue a : atr) {
    189                 if (Arrays.equals(a.getType().getOid(), MESSAGE_DIGEST_OID)) {
    190                     if (existingDigest != null) {
    191                         throw new SecurityException("Too many MessageDigest attributes");
    192                     }
    193                     Collection<?> entries = a.getValue().getValues(ASN1OctetString.getInstance());
    194                     if (entries.size() != 1) {
    195                         throw new SecurityException("Too many values for MessageDigest attribute");
    196                     }
    197                     existingDigest = (byte[]) entries.iterator().next();
    198                 }
    199             }
    200 
    201             // RFC 2315 section 9.1: if authenticatedAttributes is present, it
    202             // must have a message-digest attribute.
    203             if (existingDigest == null) {
    204                 throw new SecurityException("Missing MessageDigest in Authenticated Attributes");
    205             }
    206 
    207             MessageDigest md = null;
    208             if (daOid != null) {
    209                 md = MessageDigest.getInstance(daOid);
    210             }
    211             if (md == null && daName != null) {
    212                 md = MessageDigest.getInstance(daName);
    213             }
    214             if (md == null) {
    215                 return null;
    216             }
    217 
    218             byte[] computedDigest = md.digest(sfBytes);
    219             if (!Arrays.equals(existingDigest, computedDigest)) {
    220                 throw new SecurityException("Incorrect MD");
    221             }
    222         }
    223 
    224         if (!sig.verify(sigInfo.getEncryptedDigest())) {
    225             throw new SecurityException("Incorrect signature");
    226         }
    227 
    228         return createChain(certs[issuerSertIndex], certs);
    229     }
    230 
    231     private static X509Certificate[] createChain(X509Certificate signer,
    232             X509Certificate[] candidates) {
    233         Principal issuer = signer.getIssuerDN();
    234 
    235         // Signer is self-signed
    236         if (signer.getSubjectDN().equals(issuer)) {
    237             return new X509Certificate[] { signer };
    238         }
    239 
    240         ArrayList<X509Certificate> chain = new ArrayList<X509Certificate>(candidates.length + 1);
    241         chain.add(0, signer);
    242 
    243         X509Certificate issuerCert;
    244         int count = 1;
    245         while (true) {
    246             issuerCert = findCert(issuer, candidates);
    247             if (issuerCert == null) {
    248                 break;
    249             }
    250             chain.add(issuerCert);
    251             count++;
    252             /* Prevent growing infinitely if there is a loop */
    253             if (count > candidates.length) {
    254                 break;
    255             }
    256             issuer = issuerCert.getIssuerDN();
    257             if (issuerCert.getSubjectDN().equals(issuer)) {
    258                 break;
    259             }
    260         }
    261         return chain.toArray(new X509Certificate[count]);
    262     }
    263 
    264     private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates) {
    265         for (int i = 0; i < candidates.length; i++) {
    266             if (issuer.equals(candidates[i].getSubjectDN())) {
    267                 return candidates[i];
    268             }
    269         }
    270         return null;
    271     }
    272 
    273     /**
    274      * For legacy reasons we need to return exactly the original encoded
    275      * certificate bytes, instead of letting the underlying implementation have
    276      * a shot at re-encoding the data.
    277      */
    278     private static class VerbatimX509Certificate extends WrappedX509Certificate {
    279         private byte[] encodedVerbatim;
    280 
    281         public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
    282             super(wrapped);
    283             this.encodedVerbatim = encodedVerbatim;
    284         }
    285 
    286         @Override
    287         public byte[] getEncoded() throws CertificateEncodingException {
    288             return encodedVerbatim;
    289         }
    290     }
    291 }
    292