| /* |
| * 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 V. Esin, Stepan M. Mishura |
| * @version $Revision$ |
| */ |
| |
| package org.apache.harmony.security.x501; |
| |
| import java.io.IOException; |
| import java.nio.charset.Charsets; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import javax.security.auth.x500.X500Principal; |
| import org.apache.harmony.security.asn1.ASN1Constants; |
| import org.apache.harmony.security.asn1.ASN1Oid; |
| import org.apache.harmony.security.asn1.ASN1Sequence; |
| import org.apache.harmony.security.asn1.ASN1StringType; |
| import org.apache.harmony.security.asn1.ASN1Type; |
| import org.apache.harmony.security.asn1.BerInputStream; |
| import org.apache.harmony.security.asn1.BerOutputStream; |
| import org.apache.harmony.security.utils.ObjectIdentifier; |
| |
| |
| /** |
| * X.501 AttributeTypeAndValue |
| */ |
| public final class AttributeTypeAndValue { |
| |
| /** known attribute types for RFC1779 (see Table 1) */ |
| private static final HashMap<String, ObjectIdentifier> RFC1779_NAMES |
| = new HashMap<String, ObjectIdentifier>(10); |
| |
| /** known keywords attribute */ |
| private static final HashMap<String, ObjectIdentifier> KNOWN_NAMES |
| = new HashMap<String, ObjectIdentifier>(30); |
| |
| /** known attribute types for RFC2253 (see 2.3. Converting AttributeTypeAndValue) */ |
| private static final HashMap<String, ObjectIdentifier> RFC2253_NAMES |
| = new HashMap<String, ObjectIdentifier>(10); |
| |
| /** known attribute types for RFC2459 (see API spec.) */ |
| private static final HashMap<String, ObjectIdentifier> RFC2459_NAMES |
| = new HashMap<String, ObjectIdentifier>(10); |
| |
| /** Country code attribute (name from RFC 1779) */ |
| private static final ObjectIdentifier C |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 6 }, "C", RFC1779_NAMES); |
| |
| /** Common name attribute (name from RFC 1779) */ |
| private static final ObjectIdentifier CN |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 3 }, "CN", RFC1779_NAMES); |
| |
| /** Domain component attribute (name from RFC 2253) */ |
| private static final ObjectIdentifier DC = new ObjectIdentifier( |
| new int[] { 0, 9, 2342, 19200300, 100, 1, 25 }, "DC", RFC2253_NAMES); |
| |
| /** DN qualifier attribute (name from API spec) */ |
| private static final ObjectIdentifier DNQ |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 46 }, "DNQ", RFC2459_NAMES); |
| |
| private static final ObjectIdentifier DNQUALIFIER |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 46 }, "DNQUALIFIER", RFC2459_NAMES); |
| |
| /** Email Address attribute (name from API spec) */ |
| private static final ObjectIdentifier EMAILADDRESS = new ObjectIdentifier( |
| new int[] { 1, 2, 840, 113549, 1, 9, 1}, "EMAILADDRESS", RFC2459_NAMES); |
| |
| /** Generation attribute (qualifies an individual's name) (name from API spec) */ |
| private static final ObjectIdentifier GENERATION |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 44 }, "GENERATION", RFC2459_NAMES); |
| |
| /** Given name attribute (name from API spec) */ |
| private static final ObjectIdentifier GIVENNAME |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 42 }, "GIVENNAME", RFC2459_NAMES); |
| |
| /** Initials attribute (initials of an individual's name) (name from API spec) */ |
| private static final ObjectIdentifier INITIALS |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 43 }, "INITIALS", RFC2459_NAMES); |
| |
| /** Name of a locality attribute (name from RFC 1779) */ |
| private static final ObjectIdentifier L |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 7 }, "L", RFC1779_NAMES); |
| |
| /** Organization name attribute (name from RFC 1779) */ |
| private static final ObjectIdentifier O |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 10 }, "O", RFC1779_NAMES); |
| |
| /** Organizational unit name attribute (name from RFC 1779) */ |
| private static final ObjectIdentifier OU |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 11 }, "OU", RFC1779_NAMES); |
| |
| /** Serial number attribute (serial number of a device) (name from API spec) */ |
| private static final ObjectIdentifier SERIALNUMBER |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 5 }, "SERIALNUMBER", RFC2459_NAMES); |
| |
| /** Attribute for the full name of a state or province (name from RFC 1779) */ |
| private static final ObjectIdentifier ST |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 8 }, "ST", RFC1779_NAMES); |
| |
| /** Street attribute (name from RFC 1779) */ |
| private static final ObjectIdentifier STREET |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 9 }, "STREET", RFC1779_NAMES); |
| |
| /** Surname attribute (comes from an individual's parent name) (name from API spec) */ |
| private static final ObjectIdentifier SURNAME |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 4 }, "SURNAME", RFC2459_NAMES); |
| |
| /** Title attribute (object in an organization)(name from API spec) */ |
| private static final ObjectIdentifier T |
| = new ObjectIdentifier(new int[] { 2, 5, 4, 12 }, "T", RFC2459_NAMES); |
| |
| /** User identifier attribute (name from RFC 2253) */ |
| private static final ObjectIdentifier UID = new ObjectIdentifier( |
| new int[]{ 0, 9, 2342, 19200300, 100, 1, 1 }, "UID", RFC2253_NAMES); |
| |
| /** pool's capacity */ |
| private static final int CAPACITY = 10; |
| |
| /** pool's size */ |
| private static final int SIZE = 10; |
| |
| /** pool: contains all recognizable attribute type keywords */ |
| private static final ObjectIdentifier[][] KNOWN_OIDS = new ObjectIdentifier[SIZE][CAPACITY]; |
| |
| static { |
| RFC1779_NAMES.put(CN.getName(), CN); |
| RFC1779_NAMES.put(L.getName(), L); |
| RFC1779_NAMES.put(ST.getName(), ST); |
| RFC1779_NAMES.put(O.getName(), O); |
| RFC1779_NAMES.put(OU.getName(), OU); |
| RFC1779_NAMES.put(C.getName(), C); |
| RFC1779_NAMES.put(STREET.getName(), STREET); |
| |
| RFC2253_NAMES.putAll(RFC1779_NAMES); |
| RFC2253_NAMES.put(DC.getName(), DC); |
| RFC2253_NAMES.put(UID.getName(), UID); |
| |
| RFC2459_NAMES.put(DNQ.getName(), DNQ); |
| RFC2459_NAMES.put(DNQUALIFIER.getName(), DNQUALIFIER); |
| RFC2459_NAMES.put(EMAILADDRESS.getName(), EMAILADDRESS); |
| RFC2459_NAMES.put(GENERATION.getName(), GENERATION); |
| RFC2459_NAMES.put(GIVENNAME.getName(), GIVENNAME); |
| RFC2459_NAMES.put(INITIALS.getName(), INITIALS); |
| RFC2459_NAMES.put(SERIALNUMBER.getName(), SERIALNUMBER); |
| RFC2459_NAMES.put(SURNAME.getName(), SURNAME); |
| RFC2459_NAMES.put(T.getName(), T); |
| |
| // add from RFC2253 (includes RFC1779) |
| for (ObjectIdentifier objectIdentifier : RFC2253_NAMES.values()) { |
| addOID(objectIdentifier); |
| } |
| |
| // add attributes from RFC2459 |
| for (ObjectIdentifier o : RFC2459_NAMES.values()) { |
| //don't add DNQUALIFIER because it has the same oid as DNQ |
| if (!(o == DNQUALIFIER)) { |
| addOID(o); |
| } |
| } |
| |
| KNOWN_NAMES.putAll(RFC2253_NAMES); // RFC2253 includes RFC1779 |
| KNOWN_NAMES.putAll(RFC2459_NAMES); |
| } |
| |
| /** Attribute type */ |
| private final ObjectIdentifier oid; |
| |
| /** Attribute value */ |
| private final AttributeValue value; |
| |
| // for decoder only |
| private AttributeTypeAndValue(int[] oid, AttributeValue value) throws IOException { |
| ObjectIdentifier thisOid = getOID(oid); |
| if (thisOid == null) { |
| thisOid = new ObjectIdentifier(oid); |
| } |
| this.oid = thisOid; |
| this.value = value; |
| } |
| |
| /** |
| * Creates AttributeTypeAndValue with OID and AttributeValue. Parses OID |
| * string representation |
| * |
| * @param sOid |
| * string representation of OID |
| * @param value |
| * attribute value |
| * @throws IOException |
| * if OID can not be created from its string representation |
| */ |
| public AttributeTypeAndValue(String sOid, AttributeValue value) throws IOException { |
| if (sOid.charAt(0) >= '0' && sOid.charAt(0) <= '9') { |
| int[] array = org.apache.harmony.security.asn1.ObjectIdentifier.toIntArray(sOid); |
| ObjectIdentifier thisOid = getOID(array); |
| if (thisOid == null) { |
| thisOid = new ObjectIdentifier(array); |
| } |
| this.oid = thisOid; |
| |
| } else { |
| this.oid = KNOWN_NAMES.get(sOid.toUpperCase(Locale.US)); |
| if (this.oid == null) { |
| throw new IOException("Unrecognizable attribute name: " + sOid); |
| } |
| } |
| this.value = value; |
| } |
| |
| /** |
| * Appends AttributeTypeAndValue string representation |
| * |
| * @param attrFormat - format of DN |
| */ |
| public void appendName(String attrFormat, StringBuilder sb) { |
| boolean hexFormat = false; |
| if (X500Principal.RFC1779.equals(attrFormat)) { |
| if (RFC1779_NAMES == oid.getGroup()) { |
| sb.append(oid.getName()); |
| } else { |
| sb.append(oid.toOIDString()); |
| } |
| |
| sb.append('='); |
| if (value.escapedString == value.getHexString()) { |
| sb.append(value.getHexString().toUpperCase(Locale.US)); |
| } else if (value.escapedString.length() != value.rawString.length()) { |
| // was escaped |
| value.appendQEString(sb); |
| } else { |
| sb.append(value.escapedString); |
| } |
| } else { |
| Object group = oid.getGroup(); |
| // RFC2253 includes names from RFC1779 |
| if (RFC1779_NAMES == group || RFC2253_NAMES == group) { |
| sb.append(oid.getName()); |
| |
| if (X500Principal.CANONICAL.equals(attrFormat)) { |
| // only PrintableString and UTF8String in string format |
| // all others are output in hex format |
| // no hex for teletex; see http://b/2102191 |
| int tag = value.getTag(); |
| if (!ASN1StringType.UTF8STRING.checkTag(tag) |
| && !ASN1StringType.PRINTABLESTRING.checkTag(tag) |
| && !ASN1StringType.TELETEXSTRING.checkTag(tag)) { |
| hexFormat = true; |
| } |
| } |
| |
| } else { |
| sb.append(oid.toString()); |
| hexFormat = true; |
| } |
| |
| sb.append('='); |
| |
| if (hexFormat) { |
| sb.append(value.getHexString()); |
| } else { |
| if (X500Principal.CANONICAL.equals(attrFormat)) { |
| sb.append(value.makeCanonical()); |
| } else { |
| sb.append(value.escapedString); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Gets type of the AttributeTypeAndValue |
| */ |
| public ObjectIdentifier getType() { |
| return oid; |
| } |
| |
| /** |
| * According to RFC 3280 (http://www.ietf.org/rfc/rfc3280.txt) |
| * X.501 AttributeTypeAndValue structure is defined as follows: |
| * |
| * AttributeTypeAndValue ::= SEQUENCE { |
| * type AttributeType, |
| * value AttributeValue } |
| * |
| * AttributeType ::= OBJECT IDENTIFIER |
| * |
| * AttributeValue ::= ANY DEFINED BY AttributeType |
| * ... |
| * DirectoryString ::= CHOICE { |
| * teletexString TeletexString (SIZE (1..MAX)), |
| * printableString PrintableString (SIZE (1..MAX)), |
| * universalString UniversalString (SIZE (1..MAX)), |
| * utf8String UTF8String (SIZE (1.. MAX)), |
| * bmpString BMPString (SIZE (1..MAX)) } |
| * |
| */ |
| public static final ASN1Type attributeValue = new ASN1Type(ASN1Constants.TAG_PRINTABLESTRING) { |
| |
| public boolean checkTag(int tag) { |
| return true; |
| } |
| |
| public Object decode(BerInputStream in) throws IOException { |
| // FIXME what about constr??? |
| String str = null; |
| if (DirectoryString.ASN1.checkTag(in.tag)) { |
| // has string representation |
| str = (String) DirectoryString.ASN1.decode(in); |
| } else { |
| // gets octets only |
| in.readContent(); |
| } |
| |
| byte[] bytesEncoded = new byte[in.getOffset() - in.getTagOffset()]; |
| System.arraycopy(in.getBuffer(), in.getTagOffset(), bytesEncoded, |
| 0, bytesEncoded.length); |
| |
| return new AttributeValue(str, bytesEncoded, in.tag); |
| } |
| |
| @Override public Object getDecodedObject(BerInputStream in) throws IOException { |
| // stub to avoid wrong decoder usage |
| throw new RuntimeException("AttributeValue getDecodedObject MUST NOT be invoked"); |
| } |
| |
| // |
| // Encode |
| // |
| public void encodeASN(BerOutputStream out) { |
| AttributeValue av = (AttributeValue) out.content; |
| |
| if (av.encoded != null) { |
| out.content = av.encoded; |
| out.encodeANY(); |
| } else { |
| out.encodeTag(av.getTag()); |
| out.content = av.bytes; |
| out.encodeString(); |
| } |
| } |
| |
| public void setEncodingContent(BerOutputStream out) { |
| AttributeValue av = (AttributeValue) out.content; |
| |
| if (av.encoded != null) { |
| out.length = av.encoded.length; |
| } else { |
| if (av.getTag() == ASN1Constants.TAG_UTF8STRING) { |
| out.content = av.rawString; |
| ASN1StringType.UTF8STRING.setEncodingContent(out); |
| av.bytes = (byte[]) out.content; |
| out.content = av; |
| } else { |
| av.bytes = av.rawString.getBytes(Charsets.UTF_8); |
| out.length = av.bytes.length; |
| } |
| } |
| } |
| |
| public void encodeContent(BerOutputStream out) { |
| // stub to avoid wrong encoder usage |
| throw new RuntimeException("AttributeValue encodeContent MUST NOT be invoked"); |
| } |
| |
| @Override public int getEncodedLength(BerOutputStream out) { //FIXME name |
| AttributeValue av = (AttributeValue) out.content; |
| if (av.encoded != null) { |
| return out.length; |
| } else { |
| return super.getEncodedLength(out); |
| } |
| } |
| }; |
| |
| public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] { |
| ASN1Oid.getInstance(), attributeValue }) { |
| |
| @Override protected Object getDecodedObject(BerInputStream in) throws IOException { |
| Object[] values = (Object[]) in.content; |
| return new AttributeTypeAndValue((int[]) values[0], (AttributeValue) values[1]); |
| } |
| |
| @Override protected void getValues(Object object, Object[] values) { |
| AttributeTypeAndValue atav = (AttributeTypeAndValue) object; |
| values[0] = atav.oid.getOid(); |
| values[1] = atav.value; |
| } |
| }; |
| |
| /** |
| * Returns known OID or null. |
| */ |
| private static ObjectIdentifier getOID(int[] oid) { |
| int index = hashIntArray(oid) % CAPACITY; |
| |
| // look for OID in the pool |
| ObjectIdentifier[] list = KNOWN_OIDS[index]; |
| for (int i = 0; list[i] != null; i++) { |
| if (Arrays.equals(oid, list[i].getOid())) { |
| return list[i]; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Adds known OID to pool. |
| * for static AttributeTypeAndValue initialization only |
| */ |
| private static void addOID(ObjectIdentifier oid) { |
| int[] newOid = oid.getOid(); |
| int index = hashIntArray(newOid) % CAPACITY; |
| |
| // look for OID in the pool |
| ObjectIdentifier[] list = KNOWN_OIDS[index]; |
| int i = 0; |
| for (; list[i] != null; i++) { |
| // check wrong static initialization: no duplicate OIDs |
| if (Arrays.equals(newOid, list[i].getOid())) { |
| throw new Error("ObjectIdentifier: invalid static initialization; " + |
| "duplicate OIDs: " + oid.getName() + " " + list[i].getName()); |
| } |
| } |
| |
| // check : to avoid NPE |
| if (i == (CAPACITY - 1)) { |
| throw new Error("ObjectIdentifier: invalid static initialization; " + |
| "small OID pool capacity"); |
| } |
| list[i] = oid; |
| } |
| |
| /** |
| * Returns hash for array of integers. |
| */ |
| private static int hashIntArray(int[] oid) { |
| int intHash = 0; |
| for (int i = 0; i < oid.length && i < 4; i++) { |
| intHash += oid[i] << (8 * i); //TODO what about to find better one? |
| } |
| return intHash & 0x7FFFFFFF; // only positive |
| } |
| } |