/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Alexander Y. Kleymenov * @version $Revision$ */ package org.apache.harmony.security.x509; import java.io.IOException; import java.util.Arrays; import org.apache.harmony.security.asn1.ASN1Boolean; import org.apache.harmony.security.asn1.ASN1OctetString; import org.apache.harmony.security.asn1.ASN1Oid; import org.apache.harmony.security.asn1.ASN1Sequence; import org.apache.harmony.security.asn1.ASN1Type; import org.apache.harmony.security.asn1.BerInputStream; import org.apache.harmony.security.asn1.ObjectIdentifier; import org.apache.harmony.security.utils.Array; /** * The class encapsulates the ASN.1 DER encoding/decoding work * with the Extension part of X.509 certificate * (as specified in RFC 3280 - * Internet X.509 Public Key Infrastructure. * Certificate and Certificate Revocation List (CRL) Profile. * http://www.ietf.org/rfc/rfc3280.txt): * * <pre> * Extension ::= SEQUENCE { * extnID OBJECT IDENTIFIER, * critical BOOLEAN DEFAULT FALSE, * extnValue OCTET STRING * } * </pre> */ public final class Extension { // critical constants public static final boolean CRITICAL = true; public static final boolean NON_CRITICAL = false; // constants: the extension OIDs // certificate extensions: static final int[] SUBJ_DIRECTORY_ATTRS = {2, 5, 29, 9}; static final int[] SUBJ_KEY_ID = {2, 5, 29, 14}; static final int[] KEY_USAGE = {2, 5, 29, 15}; static final int[] PRIVATE_KEY_USAGE_PERIOD = {2, 5, 29, 16}; static final int[] SUBJECT_ALT_NAME = {2, 5, 29, 17}; static final int[] ISSUER_ALTERNATIVE_NAME = {2, 5, 29, 18}; static final int[] BASIC_CONSTRAINTS = {2, 5, 29, 19}; static final int[] NAME_CONSTRAINTS = {2, 5, 29, 30}; static final int[] CRL_DISTR_POINTS = {2, 5, 29, 31}; static final int[] CERTIFICATE_POLICIES = {2, 5, 29, 32}; static final int[] POLICY_MAPPINGS = {2, 5, 29, 33}; static final int[] AUTH_KEY_ID = {2, 5, 29, 35}; static final int[] POLICY_CONSTRAINTS = {2, 5, 29, 36}; static final int[] EXTENDED_KEY_USAGE = {2, 5, 29, 37}; static final int[] FRESHEST_CRL = {2, 5, 29, 46}; static final int[] INHIBIT_ANY_POLICY = {2, 5, 29, 54}; static final int[] AUTHORITY_INFO_ACCESS = {1, 3, 6, 1, 5, 5, 7, 1, 1}; static final int[] SUBJECT_INFO_ACCESS = {1, 3, 6, 1, 5, 5, 7, 1, 11}; // crl extensions: static final int[] ISSUING_DISTR_POINT = {2, 5, 29, 28}; // crl entry extensions: static final int[] CRL_NUMBER = {2, 5, 29, 20}; static final int[] CERTIFICATE_ISSUER = {2, 5, 29, 29}; static final int[] INVALIDITY_DATE = {2, 5, 29, 24}; static final int[] REASON_CODE = {2, 5, 29, 21}; static final int[] ISSUING_DISTR_POINTS = {2, 5, 29, 28}; // the value of extnID field of the structure private final int[] extnID; private String extnID_str; // the value of critical field of the structure private final boolean critical; // the value of extnValue field of the structure private final byte[] extnValue; // the ASN.1 encoded form of Extension private byte[] encoding; // the raw (not decoded) value of extnValue field of the structure private byte[] rawExtnValue; // the decoded extension value protected ExtensionValue extnValueObject; // tells whether extension value has been decoded or not private boolean valueDecoded = false; public Extension(String extnID, boolean critical, ExtensionValue extnValueObject) { this.extnID_str = extnID; this.extnID = ObjectIdentifier.toIntArray(extnID); this.critical = critical; this.extnValueObject = extnValueObject; this.valueDecoded = true; this.extnValue = extnValueObject.getEncoded(); } public Extension(String extnID, boolean critical, byte[] extnValue) { this.extnID_str = extnID; this.extnID = ObjectIdentifier.toIntArray(extnID); this.critical = critical; this.extnValue = extnValue; } public Extension(int[] extnID, boolean critical, byte[] extnValue) { this.extnID = extnID; this.critical = critical; this.extnValue = extnValue; } public Extension(String extnID, byte[] extnValue) { this(extnID, NON_CRITICAL, extnValue); } public Extension(int[] extnID, byte[] extnValue) { this(extnID, NON_CRITICAL, extnValue); } private Extension(int[] extnID, boolean critical, byte[] extnValue, byte[] rawExtnValue, byte[] encoding, ExtensionValue decodedExtValue) { this(extnID, critical, extnValue); this.rawExtnValue = rawExtnValue; this.encoding = encoding; this.extnValueObject = decodedExtValue; this.valueDecoded = (decodedExtValue != null); } /** * Returns the value of extnID field of the structure. */ public String getExtnID() { if (extnID_str == null) { extnID_str = ObjectIdentifier.toString(extnID); } return extnID_str; } /** * Returns the value of critical field of the structure. */ public boolean getCritical() { return critical; } /** * Returns the value of extnValue field of the structure. */ public byte[] getExtnValue() { return extnValue; } /** * Returns the raw (undecoded octet string) value of extnValue * field of the structure. */ public byte[] getRawExtnValue() { if (rawExtnValue == null) { rawExtnValue = ASN1OctetString.getInstance().encode(extnValue); } return rawExtnValue; } /** * Returns ASN.1 encoded form of this X.509 Extension value. */ public byte[] getEncoded() { if (encoding == null) { encoding = Extension.ASN1.encode(this); } return encoding; } @Override public boolean equals(Object ext) { if (!(ext instanceof Extension)) { return false; } Extension extension = (Extension) ext; return Arrays.equals(extnID, extension.extnID) && (critical == extension.critical) && Arrays.equals(extnValue, extension.extnValue); } @Override public int hashCode() { return (Arrays.hashCode(extnID) * 37 + (critical ? 1 : 0)) * 37 + Arrays.hashCode(extnValue); } public ExtensionValue getDecodedExtensionValue() throws IOException { if (!valueDecoded) { decodeExtensionValue(); } return extnValueObject; } public KeyUsage getKeyUsageValue() { if (!valueDecoded) { try { decodeExtensionValue(); } catch (IOException ignored) { } } if (extnValueObject instanceof KeyUsage) { return (KeyUsage) extnValueObject; } else { return null; } } public BasicConstraints getBasicConstraintsValue() { if (!valueDecoded) { try { decodeExtensionValue(); } catch (IOException ignored) { } } if (extnValueObject instanceof BasicConstraints) { return (BasicConstraints) extnValueObject; } else { return null; } } private void decodeExtensionValue() throws IOException { if (valueDecoded) { return; } valueDecoded = true; if (Arrays.equals(extnID, SUBJ_KEY_ID)) { extnValueObject = SubjectKeyIdentifier.decode(extnValue); } else if (Arrays.equals(extnID, KEY_USAGE)) { extnValueObject = new KeyUsage(extnValue); } else if (Arrays.equals(extnID, SUBJECT_ALT_NAME)) { extnValueObject = new AlternativeName( AlternativeName.SUBJECT, extnValue); } else if (Arrays.equals(extnID, ISSUER_ALTERNATIVE_NAME)) { extnValueObject = new AlternativeName( AlternativeName.SUBJECT, extnValue); } else if (Arrays.equals(extnID, BASIC_CONSTRAINTS)) { extnValueObject = new BasicConstraints(extnValue); } else if (Arrays.equals(extnID, NAME_CONSTRAINTS)) { extnValueObject = NameConstraints.decode(extnValue); } else if (Arrays.equals(extnID, CERTIFICATE_POLICIES)) { extnValueObject = CertificatePolicies.decode(extnValue); } else if (Arrays.equals(extnID, AUTH_KEY_ID)) { extnValueObject = AuthorityKeyIdentifier.decode(extnValue); } else if (Arrays.equals(extnID, POLICY_CONSTRAINTS)) { extnValueObject = new PolicyConstraints(extnValue); } else if (Arrays.equals(extnID, EXTENDED_KEY_USAGE)) { extnValueObject = new ExtendedKeyUsage(extnValue); } else if (Arrays.equals(extnID, INHIBIT_ANY_POLICY)) { extnValueObject = new InhibitAnyPolicy(extnValue); } else if (Arrays.equals(extnID, CERTIFICATE_ISSUER)) { extnValueObject = new CertificateIssuer(extnValue); } else if (Arrays.equals(extnID, CRL_DISTR_POINTS)) { extnValueObject = CRLDistributionPoints.decode(extnValue); } else if (Arrays.equals(extnID, CERTIFICATE_ISSUER)) { extnValueObject = new ReasonCode(extnValue); } else if (Arrays.equals(extnID, INVALIDITY_DATE)) { extnValueObject = new InvalidityDate(extnValue); } else if (Arrays.equals(extnID, REASON_CODE)) { extnValueObject = new ReasonCode(extnValue); } else if (Arrays.equals(extnID, CRL_NUMBER)) { extnValueObject = new CRLNumber(extnValue); } else if (Arrays.equals(extnID, ISSUING_DISTR_POINTS)) { extnValueObject = IssuingDistributionPoint.decode(extnValue); } else if (Arrays.equals(extnID, AUTHORITY_INFO_ACCESS)) { extnValueObject = InfoAccessSyntax.decode(extnValue); } else if (Arrays.equals(extnID, SUBJECT_INFO_ACCESS)) { extnValueObject = InfoAccessSyntax.decode(extnValue); } } public void dumpValue(StringBuilder sb, String prefix) { sb.append("OID: ").append(getExtnID()).append(", Critical: ").append(critical).append('\n'); if (!valueDecoded) { try { decodeExtensionValue(); } catch (IOException ignored) { } } if (extnValueObject != null) { extnValueObject.dumpValue(sb, prefix); return; } // else: dump unparsed hex representation sb.append(prefix); if (Arrays.equals(extnID, SUBJ_DIRECTORY_ATTRS)) { sb.append("Subject Directory Attributes Extension"); } else if (Arrays.equals(extnID, SUBJ_KEY_ID)) { sb.append("Subject Key Identifier Extension"); } else if (Arrays.equals(extnID, KEY_USAGE)) { sb.append("Key Usage Extension"); } else if (Arrays.equals(extnID, PRIVATE_KEY_USAGE_PERIOD)) { sb.append("Private Key Usage Period Extension"); } else if (Arrays.equals(extnID, SUBJECT_ALT_NAME)) { sb.append("Subject Alternative Name Extension"); } else if (Arrays.equals(extnID, ISSUER_ALTERNATIVE_NAME)) { sb.append("Issuer Alternative Name Extension"); } else if (Arrays.equals(extnID, BASIC_CONSTRAINTS)) { sb.append("Basic Constraints Extension"); } else if (Arrays.equals(extnID, NAME_CONSTRAINTS)) { sb.append("Name Constraints Extension"); } else if (Arrays.equals(extnID, CRL_DISTR_POINTS)) { sb.append("CRL Distribution Points Extension"); } else if (Arrays.equals(extnID, CERTIFICATE_POLICIES)) { sb.append("Certificate Policies Extension"); } else if (Arrays.equals(extnID, POLICY_MAPPINGS)) { sb.append("Policy Mappings Extension"); } else if (Arrays.equals(extnID, AUTH_KEY_ID)) { sb.append("Authority Key Identifier Extension"); } else if (Arrays.equals(extnID, POLICY_CONSTRAINTS)) { sb.append("Policy Constraints Extension"); } else if (Arrays.equals(extnID, EXTENDED_KEY_USAGE)) { sb.append("Extended Key Usage Extension"); } else if (Arrays.equals(extnID, INHIBIT_ANY_POLICY)) { sb.append("Inhibit Any-Policy Extension"); } else if (Arrays.equals(extnID, AUTHORITY_INFO_ACCESS)) { sb.append("Authority Information Access Extension"); } else if (Arrays.equals(extnID, SUBJECT_INFO_ACCESS)) { sb.append("Subject Information Access Extension"); } else if (Arrays.equals(extnID, INVALIDITY_DATE)) { sb.append("Invalidity Date Extension"); } else if (Arrays.equals(extnID, CRL_NUMBER)) { sb.append("CRL Number Extension"); } else if (Arrays.equals(extnID, REASON_CODE)) { sb.append("Reason Code Extension"); } else { sb.append("Unknown Extension"); } sb.append('\n').append(prefix).append("Unparsed Extension Value:\n"); sb.append(Array.toString(extnValue, prefix)); } /** * X.509 Extension encoder/decoder. */ public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] { ASN1Oid.getInstance(), ASN1Boolean.getInstance(), new ASN1OctetString() { @Override public Object getDecodedObject(BerInputStream in) throws IOException { // first - decoded octet string, // second - raw encoding of octet string return new Object[] {super.getDecodedObject(in), in.getEncoded()}; } } }) { { setDefault(Boolean.FALSE, 1); } @Override protected Object getDecodedObject(BerInputStream in) throws IOException { Object[] values = (Object[]) in.content; int[] oid = (int[]) values[0]; byte[] extnValue = (byte[]) ((Object[]) values[2])[0]; byte[] rawExtnValue = (byte[]) ((Object[]) values[2])[1]; ExtensionValue decodedExtValue = null; // decode Key Usage and Basic Constraints extension values if (Arrays.equals(oid, KEY_USAGE)) { decodedExtValue = new KeyUsage(extnValue); } else if (Arrays.equals(oid, BASIC_CONSTRAINTS)) { decodedExtValue = new BasicConstraints(extnValue); } return new Extension((int[]) values[0], (Boolean) values[1], extnValue, rawExtnValue, in.getEncoded(), decodedExtValue); } @Override protected void getValues(Object object, Object[] values) { Extension ext = (Extension) object; values[0] = ext.extnID; values[1] = (ext.critical) ? Boolean.TRUE : Boolean.FALSE; values[2] = ext.extnValue; } }; }