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