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