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