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*/
22package org.apache.harmony.security.utils;
23
24import java.io.IOException;
25import java.io.InputStream;
26import java.math.BigInteger;
27import java.security.GeneralSecurityException;
28import java.security.MessageDigest;
29import java.security.NoSuchAlgorithmException;
30import java.security.Principal;
31import java.security.Signature;
32import java.security.cert.Certificate;
33import java.security.cert.X509Certificate;
34import java.util.Arrays;
35import java.util.Collection;
36import java.util.LinkedList;
37import java.util.List;
38import javax.security.auth.x500.X500Principal;
39
40import org.apache.harmony.security.asn1.ASN1OctetString;
41import org.apache.harmony.security.asn1.BerInputStream;
42import org.apache.harmony.security.pkcs7.ContentInfo;
43import org.apache.harmony.security.pkcs7.SignedData;
44import org.apache.harmony.security.pkcs7.SignerInfo;
45import org.apache.harmony.security.provider.cert.X509CertImpl;
46import org.apache.harmony.security.x501.AttributeTypeAndValue;
47
48public class JarUtils {
49
50    // as defined in PKCS #9: Selected Attribute Types:
51    // http://www.ietf.org/rfc/rfc2985.txt
52    private static final int[] MESSAGE_DIGEST_OID =
53        new int[] {1, 2, 840, 113549, 1, 9, 4};
54
55    /**
56     * This method handle all the work with  PKCS7, ASN1 encoding, signature verifying,
57     * and certification path building.
58     * See also PKCS #7: Cryptographic Message Syntax Standard:
59     * http://www.ietf.org/rfc/rfc2315.txt
60     * @param signature - the input stream of signature file to be verified
61     * @param signatureBlock - the input stream of corresponding signature block file
62     * @return array of certificates used to verify the signature file
63     * @throws IOException - if some errors occurs during reading from the stream
64     * @throws GeneralSecurityException - if signature verification process fails
65     */
66    public static Certificate[] verifySignature(InputStream signature, InputStream
67            signatureBlock) throws IOException, GeneralSecurityException {
68
69        BerInputStream bis = new BerInputStream(signatureBlock);
70        ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
71        SignedData signedData = info.getSignedData();
72        if (signedData == null) {
73            throw new IOException("No SignedData found");
74        }
75        Collection<org.apache.harmony.security.x509.Certificate> encCerts
76                = signedData.getCertificates();
77        if (encCerts.isEmpty()) {
78            return null;
79        }
80        X509Certificate[] certs = new X509Certificate[encCerts.size()];
81        int i = 0;
82        for (org.apache.harmony.security.x509.Certificate encCert : encCerts) {
83            certs[i++] = new X509CertImpl(encCert);
84        }
85
86        List<SignerInfo> sigInfos = signedData.getSignerInfos();
87        SignerInfo sigInfo;
88        if (!sigInfos.isEmpty()) {
89            sigInfo = sigInfos.get(0);
90        } else {
91            return null;
92        }
93
94        // Issuer
95        X500Principal issuer = sigInfo.getIssuer();
96
97        // Certificate serial number
98        BigInteger snum = sigInfo.getSerialNumber();
99
100        // Locate the certificate
101        int issuerSertIndex = 0;
102        for (i = 0; i < certs.length; i++) {
103            if (issuer.equals(certs[i].getIssuerDN()) &&
104                    snum.equals(certs[i].getSerialNumber())) {
105                issuerSertIndex = i;
106                break;
107            }
108        }
109        if (i == certs.length) { // No issuer certificate found
110            return null;
111        }
112
113        if (certs[issuerSertIndex].hasUnsupportedCriticalExtension()) {
114            throw new SecurityException("Can not recognize a critical extension");
115        }
116
117        // Get Signature instance
118        final String daOid = sigInfo.getDigestAlgorithm();
119        final String daName = sigInfo.getDigestAlgorithmName();
120        final String deaOid = sigInfo.getDigestEncryptionAlgorithm();
121
122        String alg = null;
123        Signature sig = null;
124
125        if (daOid != null && deaOid != null) {
126            alg = daOid + "with" + deaOid;
127            try {
128                sig = Signature.getInstance(alg);
129            } catch (NoSuchAlgorithmException e) {
130            }
131
132            // Try to convert to names instead of OID.
133            if (sig == null) {
134                final String deaName = sigInfo.getDigestEncryptionAlgorithmName();
135                alg = daName + "with" + deaName;
136                try {
137                    sig = Signature.getInstance(alg);
138                } catch (NoSuchAlgorithmException e) {
139                }
140            }
141        }
142
143        /*
144         * TODO figure out the case in which we'd only use digestAlgorithm and
145         * add a test for it.
146         */
147        if (sig == null && daOid != null) {
148            alg = daOid;
149            try {
150                sig = Signature.getInstance(alg);
151            } catch (NoSuchAlgorithmException e) {
152            }
153
154            if (sig == null && daName != null) {
155                alg = daName;
156                try {
157                    sig = Signature.getInstance(alg);
158                } catch (NoSuchAlgorithmException e) {
159                }
160            }
161        }
162
163        // We couldn't find a valid Signature type.
164        if (sig == null) {
165            return null;
166        }
167
168        sig.initVerify(certs[issuerSertIndex]);
169
170        // If the authenticatedAttributes field of SignerInfo contains more than zero attributes,
171        // compute the message digest on the ASN.1 DER encoding of the Attributes value.
172        // Otherwise, compute the message digest on the data.
173        List<AttributeTypeAndValue> atr = sigInfo.getAuthenticatedAttributes();
174
175        byte[] sfBytes = new byte[signature.available()];
176        signature.read(sfBytes);
177
178        if (atr == null) {
179            sig.update(sfBytes);
180        } else {
181            sig.update(sigInfo.getEncodedAuthenticatedAttributes());
182
183            // If the authenticatedAttributes field contains the message-digest attribute,
184            // verify that it equals the computed digest of the signature file
185            byte[] existingDigest = null;
186            for (AttributeTypeAndValue a : atr) {
187                if (Arrays.equals(a.getType().getOid(), MESSAGE_DIGEST_OID)) {
188                    if (existingDigest != null) {
189                        throw new SecurityException("Too many MessageDigest attributes");
190                    }
191                    Collection<?> entries = a.getValue().getValues(ASN1OctetString.getInstance());
192                    if (entries.size() != 1) {
193                        throw new SecurityException("Too many values for MessageDigest attribute");
194                    }
195                    existingDigest = (byte[]) entries.iterator().next();
196                }
197            }
198
199            // RFC 3852 section 9.2: it authAttrs is present, it must have a
200            // message digest entry.
201            if (existingDigest == null) {
202                throw new SecurityException("Missing MessageDigest in Authenticated Attributes");
203            }
204
205            MessageDigest md = null;
206            if (daOid != null) {
207                md = MessageDigest.getInstance(daOid);
208            }
209            if (md == null && daName != null) {
210                md = MessageDigest.getInstance(daName);
211            }
212            if (md == null) {
213                return null;
214            }
215
216            byte[] computedDigest = md.digest(sfBytes);
217            if (!Arrays.equals(existingDigest, computedDigest)) {
218                throw new SecurityException("Incorrect MD");
219            }
220        }
221
222        if (!sig.verify(sigInfo.getEncryptedDigest())) {
223            throw new SecurityException("Incorrect signature");
224        }
225
226        return createChain(certs[issuerSertIndex], certs);
227    }
228
229    private static X509Certificate[] createChain(X509Certificate  signer, X509Certificate[] candidates) {
230        LinkedList chain = new LinkedList();
231        chain.add(0, signer);
232
233        // Signer is self-signed
234        if (signer.getSubjectDN().equals(signer.getIssuerDN())){
235            return (X509Certificate[])chain.toArray(new X509Certificate[1]);
236        }
237
238        Principal issuer = signer.getIssuerDN();
239        X509Certificate issuerCert;
240        int count = 1;
241        while (true) {
242            issuerCert = findCert(issuer, candidates);
243            if( issuerCert == null) {
244                break;
245            }
246            chain.add(issuerCert);
247            count++;
248            if (issuerCert.getSubjectDN().equals(issuerCert.getIssuerDN())) {
249                break;
250            }
251            issuer = issuerCert.getIssuerDN();
252        }
253        return (X509Certificate[])chain.toArray(new X509Certificate[count]);
254    }
255
256    private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates) {
257        for (int i = 0; i < candidates.length; i++) {
258            if (issuer.equals(candidates[i].getSubjectDN())) {
259                return candidates[i];
260            }
261        }
262        return null;
263    }
264
265}
266