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.Arrays;
27
28import org.apache.harmony.security.asn1.ASN1Boolean;
29import org.apache.harmony.security.asn1.ASN1OctetString;
30import org.apache.harmony.security.asn1.ASN1Oid;
31import org.apache.harmony.security.asn1.ASN1Sequence;
32import org.apache.harmony.security.asn1.ASN1Type;
33import org.apache.harmony.security.asn1.BerInputStream;
34import org.apache.harmony.security.asn1.ObjectIdentifier;
35import org.apache.harmony.security.utils.Array;
36
37/**
38 * The class encapsulates the ASN.1 DER encoding/decoding work
39 * with the Extension part of X.509 certificate
40 * (as specified in RFC 3280 -
41 *  Internet X.509 Public Key Infrastructure.
42 *  Certificate and Certificate Revocation List (CRL) Profile.
43 *  http://www.ietf.org/rfc/rfc3280.txt):
44 *
45 * <pre>
46 *  Extension  ::=  SEQUENCE  {
47 *       extnID      OBJECT IDENTIFIER,
48 *       critical    BOOLEAN DEFAULT FALSE,
49 *       extnValue   OCTET STRING
50 *  }
51 * </pre>
52 */
53
54public class Extension {
55    // critical constants
56    public static final boolean CRITICAL = true;
57    public static final boolean NON_CRITICAL = false;
58
59    // constants: the extension OIDs
60    // certificate extensions:
61    static final int[] SUBJ_DIRECTORY_ATTRS = {2, 5, 29, 9};
62    static final int[] SUBJ_KEY_ID = {2, 5, 29, 14};
63    static final int[] KEY_USAGE = {2, 5, 29, 15};
64    static final int[] PRIVATE_KEY_USAGE_PERIOD = {2, 5, 29, 16};
65    static final int[] SUBJECT_ALT_NAME = {2, 5, 29, 17};
66    static final int[] ISSUER_ALTERNATIVE_NAME = {2, 5, 29, 18};
67    static final int[] BASIC_CONSTRAINTS = {2, 5, 29, 19};
68    static final int[] NAME_CONSTRAINTS = {2, 5, 29, 30};
69    static final int[] CRL_DISTR_POINTS = {2, 5, 29, 31};
70    static final int[] CERTIFICATE_POLICIES = {2, 5, 29, 32};
71    static final int[] POLICY_MAPPINGS = {2, 5, 29, 33};
72    static final int[] AUTH_KEY_ID = {2, 5, 29, 35};
73    static final int[] POLICY_CONSTRAINTS = {2, 5, 29, 36};
74    static final int[] EXTENDED_KEY_USAGE = {2, 5, 29, 37};
75    static final int[] FRESHEST_CRL = {2, 5, 29, 46};
76    static final int[] INHIBIT_ANY_POLICY = {2, 5, 29, 54};
77    static final int[] AUTHORITY_INFO_ACCESS =
78                                            {1, 3, 6, 1, 5, 5, 7, 1, 1};
79    static final int[] SUBJECT_INFO_ACCESS =
80                                            {1, 3, 6, 1, 5, 5, 7, 1, 11};
81    // crl extensions:
82    static final int[] ISSUING_DISTR_POINT = {2, 5, 29, 28};
83    // crl entry extensions:
84    static final int[] CRL_NUMBER = {2, 5, 29, 20};
85    static final int[] CERTIFICATE_ISSUER = {2, 5, 29, 29};
86    static final int[] INVALIDITY_DATE = {2, 5, 29, 24};
87    static final int[] REASON_CODE = {2, 5, 29, 21};
88    static final int[] ISSUING_DISTR_POINTS = {2, 5, 29, 28};
89
90    // the value of extnID field of the structure
91    private final int[] extnID;
92    private String extnID_str;
93    // the value of critical field of the structure
94    private final boolean critical;
95    // the value of extnValue field of the structure
96    private final byte[] extnValue;
97    // the ASN.1 encoded form of Extension
98    private byte[] encoding;
99    // the raw (not decoded) value of extnValue field of the structure
100    private byte[] rawExtnValue;
101    // the decoded extension value
102    protected ExtensionValue extnValueObject;
103    // tells whether extension value has been decoded or not
104    private boolean valueDecoded = false;
105
106    /**
107     * TODO
108     * @param   extnID: String
109     * @param   critical:   boolean
110     * @param   extnValue:  byte[]
111     */
112    public Extension(String extnID, boolean critical,
113            ExtensionValue extnValueObject) {
114        this.extnID_str = extnID;
115        this.extnID = ObjectIdentifier.toIntArray(extnID);
116        this.critical = critical;
117        this.extnValueObject = extnValueObject;
118        this.valueDecoded = true;
119        this.extnValue = extnValueObject.getEncoded();
120    }
121
122    /**
123     * TODO
124     * @param   extnID: String
125     * @param   critical:   boolean
126     * @param   extnValue:  byte[]
127     */
128    public Extension(String extnID, boolean critical, byte[] extnValue) {
129        this.extnID_str = extnID;
130        this.extnID = ObjectIdentifier.toIntArray(extnID);
131        this.critical = critical;
132        this.extnValue = extnValue;
133    }
134
135    /**
136     * TODO
137     * @param   extnID: int[]
138     * @param   critical:   boolean
139     * @param   extnValue:  byte[]
140     */
141    public Extension(int[] extnID, boolean critical, byte[] extnValue) {
142        this.extnID = extnID;
143        this.critical = critical;
144        this.extnValue = extnValue;
145    }
146
147    /**
148     * TODO
149     * @param   extnID: String
150     * @param   extnValue:  byte[]
151     */
152    public Extension(String extnID, byte[] extnValue) {
153        this(extnID, NON_CRITICAL, extnValue);
154    }
155
156    /**
157     * TODO
158     * @param   extnID: int[]
159     * @param   extnValue:  byte[]
160     */
161    public Extension(int[] extnID, byte[] extnValue) {
162        this(extnID, NON_CRITICAL, extnValue);
163    }
164
165    //
166    // TODO
167    // @param   extnID: int[]
168    // @param   critical:   boolean
169    // @param   extnValue:  byte[]
170    // @param   encoding:   byte[]
171    //
172    private Extension(int[] extnID, boolean critical, byte[] extnValue,
173            byte[] rawExtnValue, byte[] encoding,
174            ExtensionValue decodedExtValue) {
175        this(extnID, critical, extnValue);
176        this.rawExtnValue = rawExtnValue;
177        this.encoding = encoding;
178        this.extnValueObject = decodedExtValue;
179        this.valueDecoded = (decodedExtValue != null);
180    }
181
182    /**
183     * Returns the value of extnID field of the structure.
184     * @return  extnID
185     */
186    public String getExtnID() {
187        if (extnID_str == null) {
188            extnID_str = ObjectIdentifier.toString(extnID);
189        }
190        return extnID_str;
191    }
192
193    /**
194     * Returns the value of critical field of the structure.
195     * @return  critical
196     */
197    public boolean getCritical() {
198        return critical;
199    }
200
201    /**
202     * Returns the value of extnValue field of the structure.
203     * @return  extnValue
204     */
205    public byte[] getExtnValue() {
206        return extnValue;
207    }
208
209    /**
210     * Returns the raw (undecoded octet string) value of extnValue
211     * field of the structure.
212     * @return  rawExtnValue
213     */
214    public byte[] getRawExtnValue() {
215        if (rawExtnValue == null) {
216            rawExtnValue = ASN1OctetString.getInstance().encode(extnValue);
217        }
218        return rawExtnValue;
219    }
220
221    /**
222     * Returns ASN.1 encoded form of this X.509 Extension value.
223     * @return a byte array containing ASN.1 encode form.
224     */
225    public byte[] getEncoded() {
226        if (encoding == null) {
227            encoding = Extension.ASN1.encode(this);
228        }
229        return encoding;
230    }
231
232    public boolean equals(Object ext) {
233        if (!(ext instanceof Extension)) {
234            return false;
235        }
236        Extension extn = (Extension) ext;
237        return Arrays.equals(extnID, extn.extnID)
238            && (critical == extn.critical)
239            && Arrays.equals(extnValue, extn.extnValue);
240    }
241
242    public int hashCode() {
243    	return (extnID.hashCode() * 37 + (critical ? 1 : 0)) * 37 + extnValue.hashCode();
244    }
245
246    public ExtensionValue getDecodedExtensionValue() throws IOException {
247        if (!valueDecoded) {
248            decodeExtensionValue();
249        }
250        return extnValueObject;
251    }
252
253    public KeyUsage getKeyUsageValue() {
254        if (!valueDecoded) {
255            try {
256                decodeExtensionValue();
257            } catch (IOException e) { }
258        }
259        if (extnValueObject instanceof KeyUsage) {
260            return (KeyUsage) extnValueObject;
261        } else {
262            return null;
263        }
264    }
265
266    public BasicConstraints getBasicConstraintsValue() {
267        if (!valueDecoded) {
268            try {
269                decodeExtensionValue();
270            } catch (IOException e) { }
271        }
272        if (extnValueObject instanceof BasicConstraints) {
273            return (BasicConstraints) extnValueObject;
274        } else {
275            return null;
276        }
277    }
278
279    private void decodeExtensionValue() throws IOException {
280        if (valueDecoded) {
281            return;
282        }
283        valueDecoded = true;
284        if (oidEquals(extnID, SUBJ_KEY_ID)) {
285            extnValueObject = SubjectKeyIdentifier.decode(extnValue);
286        } else if (oidEquals(extnID, KEY_USAGE)) {
287            extnValueObject = new KeyUsage(extnValue);
288        } else if (oidEquals(extnID, SUBJECT_ALT_NAME)) {
289            extnValueObject = new AlternativeName(
290                    AlternativeName.SUBJECT, extnValue);
291        } else if (oidEquals(extnID, ISSUER_ALTERNATIVE_NAME)) {
292            extnValueObject = new AlternativeName(
293                    AlternativeName.SUBJECT, extnValue);
294        } else if (oidEquals(extnID, BASIC_CONSTRAINTS)) {
295            extnValueObject = new BasicConstraints(extnValue);
296        } else if (oidEquals(extnID, NAME_CONSTRAINTS)) {
297            extnValueObject = NameConstraints.decode(extnValue);
298        } else if (oidEquals(extnID, CERTIFICATE_POLICIES)) {
299            extnValueObject = CertificatePolicies.decode(extnValue);
300        } else if (oidEquals(extnID, AUTH_KEY_ID)) {
301            extnValueObject = AuthorityKeyIdentifier.decode(extnValue);
302        } else if (oidEquals(extnID, POLICY_CONSTRAINTS)) {
303            extnValueObject = new PolicyConstraints(extnValue);
304        } else if (oidEquals(extnID, EXTENDED_KEY_USAGE)) {
305            extnValueObject = new ExtendedKeyUsage(extnValue);
306        } else if (oidEquals(extnID, INHIBIT_ANY_POLICY)) {
307            extnValueObject = new InhibitAnyPolicy(extnValue);
308        } else if (oidEquals(extnID, CERTIFICATE_ISSUER)) {
309            extnValueObject = new CertificateIssuer(extnValue);
310        } else if (oidEquals(extnID, CRL_DISTR_POINTS)) {
311            extnValueObject = CRLDistributionPoints.decode(extnValue);
312        } else if (oidEquals(extnID, CERTIFICATE_ISSUER)) {
313            extnValueObject = new ReasonCode(extnValue);
314        } else if (oidEquals(extnID, INVALIDITY_DATE)) {
315            extnValueObject = new InvalidityDate(extnValue);
316        } else if (oidEquals(extnID, REASON_CODE)) {
317            extnValueObject = new ReasonCode(extnValue);
318        } else if (oidEquals(extnID, CRL_NUMBER)) {
319            extnValueObject = new CRLNumber(extnValue);
320        } else if (oidEquals(extnID, ISSUING_DISTR_POINTS)) {
321            extnValueObject = IssuingDistributionPoint.decode(extnValue);
322        } else if (oidEquals(extnID, AUTHORITY_INFO_ACCESS)) {
323            extnValueObject = InfoAccessSyntax.decode(extnValue);
324        } else if (oidEquals(extnID, SUBJECT_INFO_ACCESS)) {
325            extnValueObject = InfoAccessSyntax.decode(extnValue);
326        }
327    }
328
329    /**
330     * Places the string representation into the StringBuffer object.
331     */
332    public void dumpValue(StringBuffer buffer, String prefix) {
333        buffer.append("OID: ").append(getExtnID()) //$NON-NLS-1$
334            .append(", Critical: ").append(critical).append('\n'); //$NON-NLS-1$
335        if (!valueDecoded) {
336            try {
337                decodeExtensionValue();
338            } catch (IOException e) { }
339        }
340        if (extnValueObject != null) {
341            extnValueObject.dumpValue(buffer, prefix);
342            return;
343        }
344        // else: dump unparsed hex representation
345        buffer.append(prefix);
346        if (oidEquals(extnID, SUBJ_DIRECTORY_ATTRS)) {
347            buffer.append("Subject Directory Attributes Extension"); //$NON-NLS-1$
348        } else if (oidEquals(extnID, SUBJ_KEY_ID)) {
349            buffer.append("Subject Key Identifier Extension"); //$NON-NLS-1$
350        } else if (oidEquals(extnID, KEY_USAGE)) {
351            buffer.append("Key Usage Extension"); //$NON-NLS-1$
352        } else if (oidEquals(extnID, PRIVATE_KEY_USAGE_PERIOD)) {
353            buffer.append("Private Key Usage Period Extension"); //$NON-NLS-1$
354        } else if (oidEquals(extnID, SUBJECT_ALT_NAME)) {
355            buffer.append("Subject Alternative Name Extension"); //$NON-NLS-1$
356        } else if (oidEquals(extnID, ISSUER_ALTERNATIVE_NAME)) {
357            buffer.append("Issuer Alternative Name Extension"); //$NON-NLS-1$
358        } else if (oidEquals(extnID, BASIC_CONSTRAINTS)) {
359            buffer.append("Basic Constraints Extension"); //$NON-NLS-1$
360        } else if (oidEquals(extnID, NAME_CONSTRAINTS)) {
361            buffer.append("Name Constraints Extension"); //$NON-NLS-1$
362        } else if (oidEquals(extnID, CRL_DISTR_POINTS)) {
363            buffer.append("CRL Distribution Points Extension"); //$NON-NLS-1$
364        } else if (oidEquals(extnID, CERTIFICATE_POLICIES)) {
365            buffer.append("Certificate Policies Extension"); //$NON-NLS-1$
366        } else if (oidEquals(extnID, POLICY_MAPPINGS)) {
367            buffer.append("Policy Mappings Extension"); //$NON-NLS-1$
368        } else if (oidEquals(extnID, AUTH_KEY_ID)) {
369            buffer.append("Authority Key Identifier Extension"); //$NON-NLS-1$
370        } else if (oidEquals(extnID, POLICY_CONSTRAINTS)) {
371            buffer.append("Policy Constraints Extension"); //$NON-NLS-1$
372        } else if (oidEquals(extnID, EXTENDED_KEY_USAGE)) {
373            buffer.append("Extended Key Usage Extension"); //$NON-NLS-1$
374        } else if (oidEquals(extnID, INHIBIT_ANY_POLICY)) {
375            buffer.append("Inhibit Any-Policy Extension"); //$NON-NLS-1$
376        } else if (oidEquals(extnID, AUTHORITY_INFO_ACCESS)) {
377            buffer.append("Authority Information Access Extension"); //$NON-NLS-1$
378        } else if (oidEquals(extnID, SUBJECT_INFO_ACCESS)) {
379            buffer.append("Subject Information Access Extension"); //$NON-NLS-1$
380        } else if (oidEquals(extnID, INVALIDITY_DATE)) {
381            buffer.append("Invalidity Date Extension"); //$NON-NLS-1$
382        } else if (oidEquals(extnID, CRL_NUMBER)) {
383            buffer.append("CRL Number Extension"); //$NON-NLS-1$
384        } else if (oidEquals(extnID, REASON_CODE)) {
385            buffer.append("Reason Code Extension"); //$NON-NLS-1$
386        } else {
387            buffer.append("Unknown Extension"); //$NON-NLS-1$
388        }
389        buffer.append('\n').append(prefix)
390            .append("Unparsed Extension Value:\n"); //$NON-NLS-1$
391        buffer.append(Array.toString(extnValue, prefix));
392    }
393
394
395    // Compares two OIDs
396    private static boolean oidEquals(int[] oid1, int[] oid2) {
397        int length = oid1.length;
398        if (length != oid2.length) {
399            return false;
400        }
401        while (length > 0) {
402            if (oid1[--length] != oid2[length]) {
403                return false;
404            }
405        }
406        return true;
407    }
408
409    /**
410     * X.509 Extension encoder/decoder.
411     */
412    public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {
413            ASN1Oid.getInstance(),
414            ASN1Boolean.getInstance(),
415            new ASN1OctetString() {
416                public Object getDecodedObject(BerInputStream in)
417                                                throws IOException {
418                    // first - decoded octet string,
419                    // second - raw encoding of octet string
420                    return new Object[]
421                        {super.getDecodedObject(in), in.getEncoded()};
422                }
423            }
424        }) {
425        {
426            setDefault(Boolean.FALSE, 1);
427        }
428
429        protected Object getDecodedObject(BerInputStream in) throws IOException {
430            Object[] values = (Object[]) in.content;
431
432            int[] oid = (int[]) values[0];
433            byte[] extnValue = (byte[]) ((Object[]) values[2])[0];
434            byte[] rawExtnValue = (byte[]) ((Object[]) values[2])[1];
435
436            ExtensionValue decodedExtValue = null;
437            // decode Key Usage and Basic Constraints extension values
438            if (oidEquals(oid, KEY_USAGE)) {
439                decodedExtValue = new KeyUsage(extnValue);
440            } else if (oidEquals(oid, BASIC_CONSTRAINTS)) {
441                decodedExtValue = new BasicConstraints(extnValue);
442            }
443
444            return
445                new Extension((int[]) values[0],
446                    ((Boolean) values[1]).booleanValue(),
447                    extnValue, rawExtnValue, in.getEncoded(), decodedExtValue);
448        }
449
450        protected void getValues(Object object, Object[] values) {
451
452            Extension ext = (Extension) object;
453
454            values[0] = ext.extnID;
455            values[1] = (ext.critical) ? Boolean.TRUE : Boolean.FALSE;
456            values[2] = ext.extnValue;
457        }
458    };
459}
460
461