| /* |
| * 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; |
| } |
| }; |
| } |