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 Alexander Y. Kleymenov
20* @version $Revision$
21*/
22
23package org.apache.harmony.security.x509;
24
25import java.io.IOException;
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.Collection;
29import java.util.Collections;
30import java.util.HashMap;
31import java.util.HashSet;
32import java.util.List;
33import java.util.Set;
34import javax.security.auth.x500.X500Principal;
35import org.apache.harmony.security.asn1.ASN1SequenceOf;
36import org.apache.harmony.security.asn1.ASN1Type;
37import org.apache.harmony.security.asn1.BerInputStream;
38
39/**
40 * The class encapsulates the ASN.1 DER encoding/decoding work
41 * with the Extensions part of X.509 certificate
42 * (as specified in RFC 3280 -
43 *  Internet X.509 Public Key Infrastructure.
44 *  Certificate and Certificate Revocation List (CRL) Profile.
45 *  http://www.ietf.org/rfc/rfc3280.txt):
46 *
47 * <pre>
48 *  Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
49 * </pre>
50 */
51public final class Extensions {
52
53    // Supported critical extensions oids:
54    private static List SUPPORTED_CRITICAL = Arrays.asList(
55            "2.5.29.15", "2.5.29.19", "2.5.29.32", "2.5.29.17",
56            "2.5.29.30", "2.5.29.36", "2.5.29.37", "2.5.29.54");
57
58    // the values of extensions of the structure
59    private final List<Extension> extensions;
60
61    // to speed up access, the following fields cache values computed
62    // from the extensions field, initialized using the "single-check
63    // idiom".
64
65    private volatile Set<String> critical;
66    private volatile Set<String> noncritical;
67    // the flag showing is there any unsupported critical extension
68    // in the list of extensions or not.
69    private volatile Boolean hasUnsupported;
70
71    // map containing the oid of extensions as a keys and
72    // Extension objects as values
73    private volatile HashMap<String, Extension> oidMap;
74
75    // the ASN.1 encoded form of Extensions
76    private byte[] encoding;
77
78    /**
79     * Constructs an object representing the value of Extensions.
80     */
81    public Extensions() {
82        this.extensions = null;
83    }
84
85    public Extensions(List<Extension> extensions) {
86        this.extensions = extensions;
87    }
88
89    public int size() {
90        return (extensions == null) ? 0 : extensions.size();
91    }
92
93    /**
94     * Returns the list of critical extensions.
95     */
96    public Set<String> getCriticalExtensions() {
97        Set<String> resultCritical = critical;
98        if (resultCritical == null) {
99            makeOidsLists();
100            resultCritical = critical;
101        }
102        return resultCritical;
103    }
104
105    /**
106     * Returns the list of critical extensions.
107     */
108    public Set<String> getNonCriticalExtensions() {
109        Set<String> resultNoncritical = noncritical;
110        if (resultNoncritical == null) {
111            makeOidsLists();
112            resultNoncritical = noncritical;
113        }
114        return resultNoncritical;
115    }
116
117    public boolean hasUnsupportedCritical() {
118        Boolean resultHasUnsupported = hasUnsupported;
119        if (resultHasUnsupported == null) {
120            makeOidsLists();
121            resultHasUnsupported = hasUnsupported;
122        }
123        return resultHasUnsupported.booleanValue();
124    }
125
126    //
127    // Makes the separated lists with oids of critical
128    // and non-critical extensions
129    //
130    private void makeOidsLists() {
131        if (extensions == null) {
132            return;
133        }
134        int size = extensions.size();
135        Set<String> localCritical = new HashSet<String>(size);
136        Set<String> localNoncritical = new HashSet<String>(size);
137        Boolean localHasUnsupported = Boolean.FALSE;
138        for (Extension extension : extensions) {
139            String oid = extension.getId();
140            if (extension.isCritical()) {
141                if (!SUPPORTED_CRITICAL.contains(oid)) {
142                    localHasUnsupported = Boolean.TRUE;
143                }
144                localCritical.add(oid);
145            } else {
146                localNoncritical.add(oid);
147            }
148        }
149        this.critical = localCritical;
150        this.noncritical = localNoncritical;
151        this.hasUnsupported = localHasUnsupported;
152    }
153
154    /**
155     * Returns the values of extensions.
156     */
157    public Extension getExtensionByOID(String oid) {
158        if (extensions == null) {
159            return null;
160        }
161        HashMap<String, Extension> localOidMap = oidMap;
162        if (localOidMap == null) {
163            localOidMap = new HashMap<String, Extension>();
164            for (Extension extension : extensions) {
165                localOidMap.put(extension.getId(), extension);
166            }
167            this.oidMap = localOidMap;
168        }
169        return localOidMap.get(oid);
170    }
171
172
173    /**
174     * Returns the value of Key Usage extension (OID == 2.5.29.15).
175     * The ASN.1 definition of Key Usage Extension is:
176     *
177     * <pre>
178     * id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
179     *
180     * KeyUsage ::= BIT STRING {
181     *     digitalSignature        (0),
182     *     nonRepudiation          (1),
183     *     keyEncipherment         (2),
184     *     dataEncipherment        (3),
185     *     keyAgreement            (4),
186     *     keyCertSign             (5),
187     *     cRLSign                 (6),
188     *     encipherOnly            (7),
189     *     decipherOnly            (8)
190     * }
191     * </pre>
192     * (as specified in RFC 3280)
193     *
194     * @return the value of Key Usage Extension if it is in the list,
195     * and null if there is no such extension or its value can not be decoded
196     * otherwise. Note, that the length of returned array can be greater
197     * than 9.
198     */
199    public boolean[] valueOfKeyUsage() {
200        Extension extension = getExtensionByOID("2.5.29.15");
201        KeyUsage kUsage;
202        if ((extension == null) || ((kUsage = extension.getKeyUsageValue()) == null)) {
203            return null;
204        }
205        return kUsage.getKeyUsage();
206    }
207
208    /**
209     * Returns the value of Extended Key Usage extension (OID == 2.5.29.37).
210     * The ASN.1 definition of Extended Key Usage Extension is:
211     *
212     * <pre>
213     *  id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
214     *
215     *  ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
216     *
217     *  KeyPurposeId ::= OBJECT IDENTIFIER
218     * </pre>
219     * (as specified in RFC 3280)
220     *
221     * @return the list with string representations of KeyPurposeId's OIDs
222     * and null
223     * @throws IOException if extension was incorrectly encoded.
224     */
225    public List<String> valueOfExtendedKeyUsage() throws IOException {
226        Extension extension = getExtensionByOID("2.5.29.37");
227        if (extension == null) {
228            return null;
229        }
230        return ((ExtendedKeyUsage) extension.getDecodedExtensionValue()).getExtendedKeyUsage();
231    }
232
233    /**
234     * Returns the value of Basic Constraints Extension (OID = 2.5.29.19).
235     * The ASN.1 definition of Basic Constraints Extension is:
236     *
237     * <pre>
238     *   id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
239     *
240     *   BasicConstraints ::= SEQUENCE {
241     *        cA                      BOOLEAN DEFAULT FALSE,
242     *        pathLenConstraint       INTEGER (0..MAX) OPTIONAL
243     *   }
244     * </pre>
245     * (as specified in RFC 3280)
246     *
247     * @return-1 if the Basic Constraints Extension is not present or
248     * it is present but it indicates the certificate is not a
249     * certificate authority. If the certificate is a certificate
250     * authority, returns the path length constraint if present, or
251     * Integer.MAX_VALUE if it is not.
252     */
253    public int valueOfBasicConstraints() {
254        Extension extension = getExtensionByOID("2.5.29.19");
255        if (extension == null) {
256            return -1;
257        }
258        BasicConstraints bc = extension.getBasicConstraintsValue();
259        if (bc == null || !bc.getCa()) {
260            return -1;
261        }
262        return bc.getPathLenConstraint();
263    }
264
265    /**
266     * Returns the value of Subject Alternative Name (OID = 2.5.29.17).
267     * The ASN.1 definition for Subject Alternative Name is:
268     *
269     * <pre>
270     *  id-ce-subjectAltName OBJECT IDENTIFIER ::=  { id-ce 17 }
271     *
272     *  SubjectAltName ::= GeneralNames
273     * </pre>
274     * (as specified in RFC 3280)
275     *
276     * @return Returns the collection of pairs:
277     * (Integer (tag), Object (name value)) if extension presents, and
278     * null if does not.
279     */
280    public Collection<List<?>> valueOfSubjectAlternativeName() throws IOException {
281        return decodeGeneralNames(getExtensionByOID("2.5.29.17"));
282    }
283
284    /**
285     * Returns the value of Issuer Alternative Name Extension (OID = 2.5.29.18).
286     * The ASN.1 definition for Issuer Alternative Name is:
287     *
288     * <pre>
289     *   id-ce-issuerAltName OBJECT IDENTIFIER ::=  { id-ce 18 }
290     *
291     *   IssuerAltName ::= GeneralNames
292     * </pre>
293     * (as specified in RFC 3280)
294     *
295     * @return Returns the collection of pairs:
296     * (Integer (tag), Object (name value)) if extension presents, and
297     * null if does not.
298     */
299    public Collection<List<?>> valueOfIssuerAlternativeName() throws IOException {
300        return decodeGeneralNames(getExtensionByOID("2.5.29.18"));
301    }
302
303    /**
304     * Given an X.509 extension that encodes GeneralNames, return it in the
305     * format expected by APIs.
306     */
307    private static Collection<List<?>> decodeGeneralNames(Extension extension)
308            throws IOException {
309        if (extension == null) {
310            return null;
311        }
312
313        Collection<List<?>> collection = ((GeneralNames) GeneralNames.ASN1.decode(extension
314                .getValue())).getPairsList();
315
316        /*
317         * If the extension had any invalid entries, we may have an empty
318         * collection at this point, so just return null.
319         */
320        if (collection.size() == 0) {
321            return null;
322        }
323
324        return Collections.unmodifiableCollection(collection);
325    }
326
327    /**
328     * Returns the value of Certificate Issuer Extension (OID = 2.5.29.29).
329     * It is a CRL entry extension and contains the GeneralNames describing
330     * the issuer of revoked certificate. Its ASN.1 notation is as follows:
331     * <pre>
332     *   id-ce-certificateIssuer   OBJECT IDENTIFIER ::= { id-ce 29 }
333     *
334     *   certificateIssuer ::=     GeneralNames
335     * </pre>
336     * (as specified in RFC 3280)
337     *
338     * @return the value of Certificate Issuer Extension
339     */
340    public X500Principal valueOfCertificateIssuerExtension() throws IOException {
341        Extension extension = getExtensionByOID("2.5.29.29");
342        if (extension == null) {
343            return null;
344        }
345        return ((CertificateIssuer) extension.getDecodedExtensionValue()).getIssuer();
346    }
347
348    /**
349     * Returns ASN.1 encoded form of this X.509 Extensions value.
350     */
351    public byte[] getEncoded() {
352        if (encoding == null) {
353            encoding = ASN1.encode(this);
354        }
355        return encoding;
356    }
357
358    @Override public boolean equals(Object other) {
359        if (!(other instanceof Extensions)) {
360            return false;
361        }
362        Extensions that = (Extensions) other;
363        return (this.extensions == null || this.extensions.isEmpty())
364                    ? (that.extensions == null || that.extensions.isEmpty())
365                    : (this.extensions.equals(that.extensions));
366    }
367
368    @Override public int hashCode() {
369        int hashCode = 0;
370        if (extensions != null) {
371            hashCode = extensions.hashCode();
372        }
373        return hashCode;
374    }
375
376    public void dumpValue(StringBuilder sb, String prefix) {
377        if (extensions == null) {
378            return;
379        }
380        int num = 1;
381        for (Extension extension: extensions) {
382            sb.append('\n').append(prefix).append('[').append(num++).append("]: ");
383            extension.dumpValue(sb, prefix);
384        }
385    }
386
387    /**
388     * Custom X.509 Extensions decoder.
389     */
390    public static final ASN1Type ASN1 = new ASN1SequenceOf(Extension.ASN1) {
391        @Override public Object getDecodedObject(BerInputStream in) {
392            return new Extensions((List<Extension>) in.content);
393        }
394
395        @Override public Collection getValues(Object object) {
396            Extensions extensions = (Extensions) object;
397            return (extensions.extensions == null) ? new ArrayList() : extensions.extensions;
398        }
399    };
400}
401