/*
* 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
*/
package org.apache.harmony.security.x509;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
import org.apache.harmony.security.asn1.ASN1SequenceOf;
import org.apache.harmony.security.asn1.ASN1Type;
import org.apache.harmony.security.asn1.BerInputStream;
/**
* The class encapsulates the ASN.1 DER encoding/decoding work with the
* Extensions 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>
* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
* </pre>
*/
public class Extensions {
// Supported critical extensions oids:
private static List<String> SUPPORTED_CRITICAL = Arrays
.asList(new String[] {
"2.5.29.15", "2.5.29.19", "2.5.29.32", "2.5.29.17", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
"2.5.29.30", "2.5.29.36", "2.5.29.37", "2.5.29.54" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
// the values of extensions of the structure
private List<Extension> extensions;
private Set<String> critical;
private Set<String> noncritical;
// the flag showing is there any unsupported critical extension
// in the list of extensions or not.
private boolean hasUnsupported;
// map containing the oid of extensions as a keys and
// Extension objects as values
private HashMap<String, Extension> oidMap;
// the ASN.1 encoded form of Extensions
private byte[] encoding;
/**
* Custom X.509 Extensions decoder.
*/
public static final ASN1Type ASN1 = new ASN1SequenceOf(Extension.ASN1) {
@Override
public Object getDecodedObject(BerInputStream in) {
return new Extensions((List<Extension>) in.content);
}
@Override
public List<Extension> getValues(Object object) {
final Extensions exts = (Extensions) object;
return (exts.extensions == null) ? new ArrayList<Extension>()
: exts.extensions;
}
};
/**
* Constructs an object representing the value of Extensions.
*/
public Extensions() {
}
/**
* TODO
*
* @param extensions
* : List
*/
public Extensions(List<Extension> extensions) {
this.extensions = extensions;
}
/**
* TODO
*
* @param extn
* : Extension
* @return
*/
public void addExtension(Extension extn) {
encoding = null;
if (extensions == null) {
extensions = new ArrayList<Extension>();
}
extensions.add(extn);
if (oidMap != null) {
oidMap.put(extn.getExtnID(), extn);
}
if (critical != null) {
final String oid = extn.getExtnID();
if (extn.getCritical()) {
if (!SUPPORTED_CRITICAL.contains(oid)) {
hasUnsupported = true;
}
critical.add(oid);
} else {
noncritical.add(oid);
}
}
}
/**
* Places the string representation into the StringBuffer object.
*/
public void dumpValue(StringBuffer buffer, String prefix) {
if (extensions == null) {
return;
}
int num = 1;
for (final Extension extension : extensions) {
buffer.append('\n').append(prefix).append('[').append(num++)
.append("]: "); //$NON-NLS-1$
extension.dumpValue(buffer, prefix);
}
}
@Override
public boolean equals(Object exts) {
if (!(exts instanceof Extensions)) {
return false;
}
final Extensions extns = (Extensions) exts;
return ((extensions == null) || (extensions.size() == 0) ? ((extns.extensions == null) || (extns.extensions
.size() == 0))
: ((extns.extensions == null) || (extns.extensions.size() == 0)) ? false
: (extensions.containsAll(extns.extensions) && (extensions
.size() == extns.extensions.size())));
}
/**
* Returns the list of critical extensions.
*
* @return extensions
*/
public Set<String> getCriticalExtensions() {
if (critical == null) {
makeOidsLists();
}
return critical;
}
/**
* Returns ASN.1 encoded form of this X.509 Extensions value.
*
* @return a byte array containing ASN.1 encode form.
*/
public byte[] getEncoded() {
if (encoding == null) {
encoding = ASN1.encode(this);
}
return encoding;
}
/**
* Returns the values of extensions.
*
* @param oid
* - the OID of needed extension.
* @return extensions
*/
public Extension getExtensionByOID(String oid) {
if (extensions == null) {
return null;
}
if (oidMap == null) {
oidMap = new HashMap<String, Extension>();
final Iterator<Extension> it = extensions.iterator();
while (it.hasNext()) {
final Extension extn = it.next();
oidMap.put(extn.getExtnID(), extn);
}
}
return oidMap.get(oid);
}
/**
* Returns the values of extensions.
*
* @return extensions
*/
public List<Extension> getExtensions() {
return extensions;
}
/**
* Returns the list of critical extensions.
*
* @return extensions
*/
public Set<String> getNonCriticalExtensions() {
if (noncritical == null) {
makeOidsLists();
}
return noncritical;
}
@Override
public int hashCode() {
int hashcode = 0;
if (extensions != null) {
hashcode = extensions.hashCode();
}
return hashcode;
}
public boolean hasUnsupportedCritical() {
if (critical == null) {
makeOidsLists();
}
return hasUnsupported;
}
//
// Makes the separated lists with oids of critical
// and non-critical extensions
//
private void makeOidsLists() {
if (extensions == null) {
return;
}
final int size = extensions.size();
critical = new HashSet<String>(size);
noncritical = new HashSet<String>(size);
for (int i = 0; i < size; i++) {
final Extension extn = extensions.get(i);
final String oid = extn.getExtnID();
if (extn.getCritical()) {
if (!SUPPORTED_CRITICAL.contains(oid)) {
hasUnsupported = true;
}
critical.add(oid);
} else {
noncritical.add(oid);
}
}
}
public int size() {
return (extensions == null) ? 0 : extensions.size();
}
/**
* Returns the value of Basic Constraints Extension (OID = 2.5.29.19). The
* ASN.1 definition of Basic Constraints Extension is:
*
* <pre>
* id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 }
*
* BasicConstraints ::= SEQUENCE {
* cA BOOLEAN DEFAULT FALSE,
* pathLenConstraint INTEGER (0..MAX) OPTIONAL
* }
* </pre>
*
* (as specified in RFC 3280)
*
* @return the value of pathLenConstraint field if extension presents, and
* Integer.MAX_VALUE if does not.
*/
public int valueOfBasicConstrains() {
final Extension extn = getExtensionByOID("2.5.29.19"); //$NON-NLS-1$
BasicConstraints bc = null;
if ((extn == null) || ((bc = extn.getBasicConstraintsValue()) == null)) {
return Integer.MAX_VALUE;
}
return bc.getPathLenConstraint();
}
/**
* Returns the value of Certificate Issuer Extension (OID = 2.5.29.29). It
* is a CRL entry extension and contains the GeneralNames describing the
* issuer of revoked certificate. Its ASN.1 notation is as follows:
*
* <pre>
* id-ce-certificateIssuer OBJECT IDENTIFIER ::= { id-ce 29 }
*
* certificateIssuer ::= GeneralNames
* </pre>
*
* (as specified in RFC 3280)
*
* @return the value of Certificate Issuer Extension
*/
public X500Principal valueOfCertificateIssuerExtension() throws IOException {
final Extension extn = getExtensionByOID("2.5.29.29"); //$NON-NLS-1$
if (extn == null) {
return null;
}
return ((CertificateIssuer) extn.getDecodedExtensionValue())
.getIssuer();
}
/**
* Returns the value of Extended Key Usage extension (OID == 2.5.29.37). The
* ASN.1 definition of Extended Key Usage Extension is:
*
* <pre>
* id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
*
* ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
*
* KeyPurposeId ::= OBJECT IDENTIFIER
* </pre>
*
* (as specified in RFC 3280)
*
* @return the list with string representations of KeyPurposeId's OIDs and
* null
* @throws IOException
* if extension was incorrectly encoded.
*/
public List<?> valueOfExtendedKeyUsage() throws IOException {
final Extension extn = getExtensionByOID("2.5.29.37"); //$NON-NLS-1$
if (extn == null) {
return null;
}
return ((ExtendedKeyUsage) extn.getDecodedExtensionValue())
.getExtendedKeyUsage();
}
/**
* Returns the value of Issuer Alternative Name Extension (OID = 2.5.29.18).
* The ASN.1 definition for Issuer Alternative Name is:
*
* <pre>
* id-ce-issuerAltName OBJECT IDENTIFIER ::= { id-ce 18 }
*
* IssuerAltName ::= GeneralNames
* </pre>
*
* (as specified in RFC 3280)
*
* @return Returns the collection of pairs: (Integer (tag), Object (name
* value)) if extension presents, and null if does not.
*/
public List<?> valueOfIssuerAlternativeName() throws IOException {
final Extension extn = getExtensionByOID("2.5.29.18"); //$NON-NLS-1$
if (extn == null) {
return null;
}
return ((GeneralNames) GeneralNames.ASN1.decode(extn.getExtnValue()))
.getPairsList();
}
/**
* Returns the value of Key Usage extension (OID == 2.5.29.15). The ASN.1
* definition of Key Usage Extension is:
*
* <pre>
* id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 }
*
* KeyUsage ::= BIT STRING {
* digitalSignature (0),
* nonRepudiation (1),
* keyEncipherment (2),
* dataEncipherment (3),
* keyAgreement (4),
* keyCertSign (5),
* cRLSign (6),
* encipherOnly (7),
* decipherOnly (8)
* }
* </pre>
*
* (as specified in RFC 3280)
*
* @return the value of Key Usage Extension if it is in the list, and null
* if there is no such extension or its value can not be decoded
* otherwise. Note, that the length of returned array can be greater
* than 9.
*/
public boolean[] valueOfKeyUsage() {
final Extension extn = getExtensionByOID("2.5.29.15"); //$NON-NLS-1$
KeyUsage kUsage = null;
if ((extn == null) || ((kUsage = extn.getKeyUsageValue()) == null)) {
return null;
}
return kUsage.getKeyUsage();
}
/**
* Returns the value of Subject Alternative Name (OID = 2.5.29.17). The
* ASN.1 definition for Subject Alternative Name is:
*
* <pre>
* id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
*
* SubjectAltName ::= GeneralNames
* </pre>
*
* (as specified in RFC 3280)
*
* @return Returns the collection of pairs: (Integer (tag), Object (name
* value)) if extension presents, and null if does not.
*/
public List<?> valueOfSubjectAlternativeName() throws IOException {
final Extension extn = getExtensionByOID("2.5.29.17"); //$NON-NLS-1$
if (extn == null) {
return null;
}
return ((GeneralNames) GeneralNames.ASN1.decode(extn.getExtnValue()))
.getPairsList();
}
}