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