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
18package java.util.jar;
19
20import java.io.IOException;
21import java.security.CodeSigner;
22import java.security.cert.CertPath;
23import java.security.cert.Certificate;
24import java.security.cert.CertificateException;
25import java.security.cert.CertificateFactory;
26import java.security.cert.X509Certificate;
27import java.util.ArrayList;
28import java.util.List;
29import java.util.zip.ZipEntry;
30
31import javax.security.auth.x500.X500Principal;
32
33/**
34 * Represents a single file in a JAR archive together with the manifest
35 * attributes and digital signatures associated with it.
36 *
37 * @see JarFile
38 * @see JarInputStream
39 */
40public class JarEntry extends ZipEntry {
41    private Attributes attributes;
42
43    JarFile parentJar;
44
45    CodeSigner signers[];
46
47    // Cached factory used to build CertPath-s in <code>getCodeSigners()</code>.
48    private CertificateFactory factory;
49
50    private boolean isFactoryChecked = false;
51
52    /**
53     * Creates a new {@code JarEntry} named name.
54     *
55     * @param name
56     *            The name of the new {@code JarEntry}.
57     */
58    public JarEntry(String name) {
59        super(name);
60    }
61
62    /**
63     * Creates a new {@code JarEntry} using the values obtained from entry.
64     *
65     * @param entry
66     *            The ZipEntry to obtain values from.
67     */
68    public JarEntry(ZipEntry entry) {
69        super(entry);
70    }
71
72    /**
73     * Returns the {@code Attributes} object associated with this entry or
74     * {@code null} if none exists.
75     *
76     * @return the {@code Attributes} for this entry.
77     * @exception IOException
78     *                If an error occurs obtaining the {@code Attributes}.
79     * @see Attributes
80     */
81    public Attributes getAttributes() throws IOException {
82        if (attributes != null || parentJar == null) {
83            return attributes;
84        }
85        Manifest manifest = parentJar.getManifest();
86        if (manifest == null) {
87            return null;
88        }
89        return attributes = manifest.getAttributes(getName());
90    }
91
92    /**
93     * Returns an array of {@code Certificate} Objects associated with this
94     * entry or {@code null} if none exists. Make sure that the everything is
95     * read from the input stream before calling this method, or else the method
96     * returns {@code null}.
97     *
98     * @return the certificate for this entry.
99     * @see java.security.cert.Certificate
100     */
101    public Certificate[] getCertificates() {
102        if (null == parentJar) {
103            return null;
104        }
105        JarVerifier jarVerifier = parentJar.verifier;
106        if (null == jarVerifier) {
107            return null;
108        }
109        return jarVerifier.getCertificates(getName());
110    }
111
112    void setAttributes(Attributes attrib) {
113        attributes = attrib;
114    }
115
116    /**
117     * Create a new {@code JarEntry} using the values obtained from the
118     * argument.
119     *
120     * @param je
121     *            The {@code JarEntry} to obtain values from.
122     */
123    public JarEntry(JarEntry je) {
124        super(je);
125        parentJar = je.parentJar;
126        attributes = je.attributes;
127        signers = je.signers;
128    }
129
130    /**
131     * Returns the code signers for the digital signatures associated with the
132     * JAR file. If there is no such code signer, it returns {@code null}. Make
133     * sure that the everything is read from the input stream before calling
134     * this method, or else the method returns {@code null}.
135     *
136     * @return the code signers for the JAR entry.
137     * @see CodeSigner
138     */
139    public CodeSigner[] getCodeSigners() {
140        if (null == signers) {
141            signers = getCodeSigners(getCertificates());
142        }
143        if (null == signers) {
144            return null;
145        }
146
147        CodeSigner[] tmp = new CodeSigner[signers.length];
148        System.arraycopy(signers, 0, tmp, 0, tmp.length);
149        return tmp;
150    }
151
152    private CodeSigner[] getCodeSigners(Certificate[] certs) {
153        if (null == certs) {
154            return null;
155        }
156
157        X500Principal prevIssuer = null;
158        ArrayList<Certificate> list = new ArrayList<Certificate>(certs.length);
159        ArrayList<CodeSigner> asigners = new ArrayList<CodeSigner>();
160
161        for (Certificate element : certs) {
162            if (!(element instanceof X509Certificate)) {
163                // Only X509Certificate-s are taken into account - see API spec.
164                continue;
165            }
166            X509Certificate x509 = (X509Certificate) element;
167            if (null != prevIssuer) {
168                X500Principal subj = x509.getSubjectX500Principal();
169                if (!prevIssuer.equals(subj)) {
170                    // Ok, this ends the previous chain,
171                    // so transform this one into CertPath ...
172                    addCodeSigner(asigners, list);
173                    // ... and start a new one
174                    list.clear();
175                }// else { it's still the same chain }
176
177            }
178            prevIssuer = x509.getIssuerX500Principal();
179            list.add(x509);
180        }
181        if (!list.isEmpty()) {
182            addCodeSigner(asigners, list);
183        }
184        if (asigners.isEmpty()) {
185            // 'signers' is 'null' already
186            return null;
187        }
188
189        CodeSigner[] tmp = new CodeSigner[asigners.size()];
190        asigners.toArray(tmp);
191        return tmp;
192
193    }
194
195    private void addCodeSigner(ArrayList<CodeSigner> asigners,
196            List<Certificate> list) {
197        CertPath certPath = null;
198        if (!isFactoryChecked) {
199            try {
200                factory = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
201            } catch (CertificateException ex) {
202                // do nothing
203            } finally {
204                isFactoryChecked = true;
205            }
206        }
207        if (null == factory) {
208            return;
209        }
210        try {
211            certPath = factory.generateCertPath(list);
212        } catch (CertificateException ex) {
213            // do nothing
214        }
215        if (null != certPath) {
216            asigners.add(new CodeSigner(certPath, null));
217        }
218    }
219}
220