Home | History | Annotate | Download | only in x501
      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 V. Esin, Stepan M. Mishura
     20 * @version $Revision$
     21 */
     22 
     23 package org.apache.harmony.security.x501;
     24 
     25 import java.io.IOException;
     26 import java.nio.charset.StandardCharsets;
     27 import java.util.Arrays;
     28 import java.util.HashMap;
     29 import java.util.Locale;
     30 import javax.security.auth.x500.X500Principal;
     31 import org.apache.harmony.security.asn1.ASN1Constants;
     32 import org.apache.harmony.security.asn1.ASN1Oid;
     33 import org.apache.harmony.security.asn1.ASN1Sequence;
     34 import org.apache.harmony.security.asn1.ASN1StringType;
     35 import org.apache.harmony.security.asn1.ASN1Type;
     36 import org.apache.harmony.security.asn1.BerInputStream;
     37 import org.apache.harmony.security.asn1.BerOutputStream;
     38 import org.apache.harmony.security.utils.ObjectIdentifier;
     39 
     40 
     41 /**
     42  * X.501 AttributeTypeAndValue
     43  */
     44 public final class AttributeTypeAndValue {
     45 
     46     /** known attribute types for RFC1779 (see Table 1) */
     47     private static final HashMap<String, ObjectIdentifier> RFC1779_NAMES
     48             = new HashMap<String, ObjectIdentifier>(10);
     49 
     50     /** known keywords attribute */
     51     private static final HashMap<String, ObjectIdentifier> KNOWN_NAMES
     52             = new HashMap<String, ObjectIdentifier>(30);
     53 
     54     /** known attribute types for RFC2253 (see 2.3.  Converting AttributeTypeAndValue) */
     55     private static final HashMap<String, ObjectIdentifier> RFC2253_NAMES
     56             = new HashMap<String, ObjectIdentifier>(10);
     57 
     58     /** known attribute types for RFC2459 (see API spec.) */
     59     private static final HashMap<String, ObjectIdentifier> RFC2459_NAMES
     60             = new HashMap<String, ObjectIdentifier>(10);
     61 
     62     /** Country code attribute (name from RFC 1779) */
     63     private static final ObjectIdentifier C
     64             = new ObjectIdentifier(new int[] { 2, 5, 4, 6 }, "C", RFC1779_NAMES);
     65 
     66     /** Common name attribute (name from RFC 1779) */
     67     private static final ObjectIdentifier CN
     68             = new ObjectIdentifier(new int[] { 2, 5, 4, 3 }, "CN", RFC1779_NAMES);
     69 
     70     /** Domain component attribute (name from RFC 2253) */
     71     public static final ObjectIdentifier DC = new ObjectIdentifier(
     72             new int[] { 0, 9, 2342, 19200300, 100, 1, 25 }, "DC", RFC2253_NAMES);
     73 
     74     /** DN qualifier attribute (name from API spec) */
     75     private static final ObjectIdentifier DNQ
     76             = new ObjectIdentifier(new int[] { 2, 5, 4, 46 }, "DNQ", RFC2459_NAMES);
     77 
     78     private static final ObjectIdentifier DNQUALIFIER
     79             = new ObjectIdentifier(new int[] { 2, 5, 4, 46 }, "DNQUALIFIER", RFC2459_NAMES);
     80 
     81     /** Email Address attribute (name from API spec) */
     82     public static final ObjectIdentifier EMAILADDRESS = new ObjectIdentifier(
     83             new int[] { 1, 2, 840, 113549, 1, 9, 1}, "EMAILADDRESS", RFC2459_NAMES);
     84 
     85     /** Generation attribute (qualifies an individual's name) (name from API spec) */
     86     private static final ObjectIdentifier GENERATION
     87             = new ObjectIdentifier(new int[] { 2, 5, 4, 44 }, "GENERATION", RFC2459_NAMES);
     88 
     89     /** Given name attribute (name from API spec) */
     90     private static final ObjectIdentifier GIVENNAME
     91             = new ObjectIdentifier(new int[] { 2, 5, 4, 42 }, "GIVENNAME", RFC2459_NAMES);
     92 
     93     /** Initials attribute (initials of an individual's name) (name from API spec) */
     94     private static final ObjectIdentifier INITIALS
     95             = new ObjectIdentifier(new int[] { 2, 5, 4, 43 }, "INITIALS", RFC2459_NAMES);
     96 
     97     /** Name of a locality attribute (name from RFC 1779) */
     98     private static final ObjectIdentifier L
     99             = new ObjectIdentifier(new int[] { 2, 5, 4, 7 }, "L", RFC1779_NAMES);
    100 
    101     /** Organization name attribute (name from RFC 1779) */
    102     private static final ObjectIdentifier O
    103             = new ObjectIdentifier(new int[] { 2, 5, 4, 10 }, "O", RFC1779_NAMES);
    104 
    105     /** Organizational unit name attribute (name from RFC 1779) */
    106     private static final ObjectIdentifier OU
    107             = new ObjectIdentifier(new int[] { 2, 5, 4, 11 }, "OU", RFC1779_NAMES);
    108 
    109     /** Serial number attribute (serial number of a device) (name from API spec) */
    110     private static final ObjectIdentifier SERIALNUMBER
    111             = new ObjectIdentifier(new int[] { 2, 5, 4, 5 }, "SERIALNUMBER", RFC2459_NAMES);
    112 
    113     /** Attribute for the full name of a state or province (name from RFC 1779) */
    114     private static final ObjectIdentifier ST
    115             = new ObjectIdentifier(new int[] { 2, 5, 4, 8 }, "ST", RFC1779_NAMES);
    116 
    117     /** Street attribute (name from RFC 1779) */
    118     private static final ObjectIdentifier STREET
    119             = new ObjectIdentifier(new int[] { 2, 5, 4, 9 }, "STREET", RFC1779_NAMES);
    120 
    121     /** Surname attribute (comes from an individual's parent name) (name from API spec) */
    122     private static final ObjectIdentifier SURNAME
    123             = new ObjectIdentifier(new int[] { 2, 5, 4, 4 }, "SURNAME", RFC2459_NAMES);
    124 
    125     /** Title attribute (object in an organization)(name from API spec) */
    126     private static final ObjectIdentifier T
    127             = new ObjectIdentifier(new int[] { 2, 5, 4, 12 }, "T", RFC2459_NAMES);
    128 
    129     /** User identifier attribute (name from RFC 2253) */
    130     private static final ObjectIdentifier UID = new ObjectIdentifier(
    131             new int[]{ 0, 9, 2342, 19200300, 100, 1, 1 }, "UID", RFC2253_NAMES);
    132 
    133     /** pool's capacity */
    134     private static final int CAPACITY = 10;
    135 
    136     /** pool's size */
    137     private static final int SIZE = 10;
    138 
    139     /** pool: contains all recognizable attribute type keywords */
    140     private static final ObjectIdentifier[][] KNOWN_OIDS = new ObjectIdentifier[SIZE][CAPACITY];
    141 
    142     static {
    143         RFC1779_NAMES.put(CN.getName(), CN);
    144         RFC1779_NAMES.put(L.getName(), L);
    145         RFC1779_NAMES.put(ST.getName(), ST);
    146         RFC1779_NAMES.put(O.getName(), O);
    147         RFC1779_NAMES.put(OU.getName(), OU);
    148         RFC1779_NAMES.put(C.getName(), C);
    149         RFC1779_NAMES.put(STREET.getName(), STREET);
    150 
    151         RFC2253_NAMES.putAll(RFC1779_NAMES);
    152         RFC2253_NAMES.put(DC.getName(), DC);
    153         RFC2253_NAMES.put(UID.getName(), UID);
    154 
    155         RFC2459_NAMES.put(DNQ.getName(), DNQ);
    156         RFC2459_NAMES.put(DNQUALIFIER.getName(), DNQUALIFIER);
    157         RFC2459_NAMES.put(EMAILADDRESS.getName(), EMAILADDRESS);
    158         RFC2459_NAMES.put(GENERATION.getName(), GENERATION);
    159         RFC2459_NAMES.put(GIVENNAME.getName(), GIVENNAME);
    160         RFC2459_NAMES.put(INITIALS.getName(), INITIALS);
    161         RFC2459_NAMES.put(SERIALNUMBER.getName(), SERIALNUMBER);
    162         RFC2459_NAMES.put(SURNAME.getName(), SURNAME);
    163         RFC2459_NAMES.put(T.getName(), T);
    164 
    165         // add from RFC2253 (includes RFC1779)
    166         for (ObjectIdentifier objectIdentifier : RFC2253_NAMES.values()) {
    167             addOID(objectIdentifier);
    168         }
    169 
    170         // add attributes from RFC2459
    171         for (ObjectIdentifier o : RFC2459_NAMES.values()) {
    172             //don't add DNQUALIFIER because it has the same oid as DNQ
    173             if (!(o == DNQUALIFIER)) {
    174                 addOID(o);
    175             }
    176         }
    177 
    178         KNOWN_NAMES.putAll(RFC2253_NAMES); // RFC2253 includes RFC1779
    179         KNOWN_NAMES.putAll(RFC2459_NAMES);
    180     }
    181 
    182     /**
    183      * Parses OID string representation.
    184      *
    185      * @param sOid
    186      *            string representation of OID
    187      *
    188      * @throws IOException
    189      *             if OID can not be created from its string representation
    190      */
    191     public static ObjectIdentifier getObjectIdentifier(String sOid) throws IOException {
    192         if (sOid.charAt(0) >= '0' && sOid.charAt(0) <= '9') {
    193             int[] array = org.apache.harmony.security.asn1.ObjectIdentifier.toIntArray(sOid);
    194             ObjectIdentifier thisOid = getOID(array);
    195             if (thisOid == null) {
    196                 thisOid = new ObjectIdentifier(array);
    197             }
    198             return thisOid;
    199 
    200         }
    201         ObjectIdentifier thisOid = KNOWN_NAMES.get(sOid.toUpperCase(Locale.US));
    202         if (thisOid == null) {
    203             throw new IOException("Unrecognizable attribute name: " + sOid);
    204         }
    205         return thisOid;
    206     }
    207 
    208     /** Attribute type */
    209     private final ObjectIdentifier oid;
    210 
    211     /** Attribute value */
    212     private final AttributeValue value;
    213 
    214     // for decoder only
    215     private AttributeTypeAndValue(int[] oid, AttributeValue value) throws IOException {
    216         ObjectIdentifier thisOid = getOID(oid);
    217         if (thisOid == null) {
    218             thisOid = new ObjectIdentifier(oid);
    219         }
    220         this.oid = thisOid;
    221         this.value = value;
    222     }
    223 
    224     /**
    225      * Creates AttributeTypeAndValue with OID and AttributeValue.
    226      *
    227      * @param oid
    228      *            object identifier
    229      * @param value
    230      *            attribute value
    231      */
    232     public AttributeTypeAndValue(ObjectIdentifier oid, AttributeValue value) throws IOException {
    233         this.oid = oid;
    234         this.value = value;
    235     }
    236 
    237     /**
    238      * Appends AttributeTypeAndValue string representation
    239      *
    240      * @param attrFormat - format of DN
    241      */
    242     public void appendName(String attrFormat, StringBuilder sb) {
    243         boolean hexFormat = false;
    244         if (X500Principal.RFC1779.equals(attrFormat)) {
    245             if (RFC1779_NAMES == oid.getGroup()) {
    246                 sb.append(oid.getName());
    247             } else {
    248                 sb.append(oid.toOIDString());
    249             }
    250 
    251             sb.append('=');
    252             if (value.escapedString == value.getHexString()) {
    253                 sb.append(value.getHexString().toUpperCase(Locale.US));
    254             } else if (value.escapedString.length() != value.rawString.length()) {
    255                 // was escaped
    256                 value.appendQEString(sb);
    257             } else {
    258                 sb.append(value.escapedString);
    259             }
    260         } else {
    261             Object group = oid.getGroup();
    262             // RFC2253 includes names from RFC1779
    263             if (RFC1779_NAMES == group || RFC2253_NAMES == group) {
    264                 sb.append(oid.getName());
    265 
    266                 if (X500Principal.CANONICAL.equals(attrFormat)) {
    267                     // only PrintableString and UTF8String in string format
    268                     // all others are output in hex format
    269                     // no hex for teletex; see http://b/2102191
    270                     int tag = value.getTag();
    271                     if (!ASN1StringType.UTF8STRING.checkTag(tag)
    272                             && !ASN1StringType.PRINTABLESTRING.checkTag(tag)
    273                             && !ASN1StringType.TELETEXSTRING.checkTag(tag)) {
    274                         hexFormat = true;
    275                     }
    276                 }
    277 
    278             } else {
    279                 sb.append(oid.toString());
    280                 hexFormat = true;
    281             }
    282 
    283             sb.append('=');
    284 
    285             if (hexFormat) {
    286                 sb.append(value.getHexString());
    287             } else {
    288                 if (X500Principal.CANONICAL.equals(attrFormat)) {
    289                     sb.append(value.makeCanonical());
    290                 } else if (X500Principal.RFC2253.equals(attrFormat)) {
    291                     sb.append(value.getRFC2253String());
    292                 } else {
    293                     sb.append(value.escapedString);
    294                 }
    295             }
    296         }
    297     }
    298 
    299     /**
    300      * Gets type of the AttributeTypeAndValue
    301      */
    302     public ObjectIdentifier getType() {
    303         return oid;
    304     }
    305 
    306     public AttributeValue getValue() {
    307         return value;
    308     }
    309 
    310     /**
    311      * According to RFC 3280 (http://www.ietf.org/rfc/rfc3280.txt)
    312      * X.501 AttributeTypeAndValue structure is defined as follows:
    313      *
    314      *   AttributeTypeAndValue ::= SEQUENCE {
    315      *      type     AttributeType,
    316      *      value    AttributeValue }
    317      *
    318      *    AttributeType ::= OBJECT IDENTIFIER
    319      *
    320      *    AttributeValue ::= ANY DEFINED BY AttributeType
    321      *    ...
    322      *    DirectoryString ::= CHOICE {
    323      *          teletexString           TeletexString (SIZE (1..MAX)),
    324      *          printableString         PrintableString (SIZE (1..MAX)),
    325      *          universalString         UniversalString (SIZE (1..MAX)),
    326      *          utf8String              UTF8String (SIZE (1.. MAX)),
    327      *          bmpString               BMPString (SIZE (1..MAX)) }
    328      *
    329      */
    330     public static final ASN1Type attributeValue = new ASN1Type(ASN1Constants.TAG_PRINTABLESTRING) {
    331 
    332         public boolean checkTag(int tag) {
    333             return true;
    334         }
    335 
    336         public Object decode(BerInputStream in) throws IOException {
    337             // FIXME what about constr???
    338             String str = null;
    339             if (DirectoryString.ASN1.checkTag(in.tag)) {
    340                 // has string representation
    341                 str = (String) DirectoryString.ASN1.decode(in);
    342             } else {
    343                 // gets octets only
    344                 in.readContent();
    345             }
    346 
    347             byte[] bytesEncoded = new byte[in.getOffset() - in.getTagOffset()];
    348             System.arraycopy(in.getBuffer(), in.getTagOffset(), bytesEncoded,
    349                     0, bytesEncoded.length);
    350 
    351             return new AttributeValue(str, bytesEncoded, in.tag);
    352         }
    353 
    354         @Override public Object getDecodedObject(BerInputStream in) throws IOException {
    355             // stub to avoid wrong decoder usage
    356             throw new RuntimeException("AttributeValue getDecodedObject MUST NOT be invoked");
    357         }
    358 
    359         //
    360         // Encode
    361         //
    362         public void encodeASN(BerOutputStream out) {
    363             AttributeValue av = (AttributeValue) out.content;
    364 
    365             if (av.encoded != null) {
    366                 out.content = av.encoded;
    367                 out.encodeANY();
    368             } else {
    369                 out.encodeTag(av.getTag());
    370                 out.content = av.bytes;
    371                 out.encodeString();
    372             }
    373         }
    374 
    375         public void setEncodingContent(BerOutputStream out) {
    376             AttributeValue av = (AttributeValue) out.content;
    377 
    378             if (av.encoded != null) {
    379                 out.length = av.encoded.length;
    380             } else {
    381                 if (av.getTag() == ASN1Constants.TAG_UTF8STRING) {
    382                     out.content = av.rawString;
    383                     ASN1StringType.UTF8STRING.setEncodingContent(out);
    384                     av.bytes = (byte[]) out.content;
    385                     out.content = av;
    386                 } else {
    387                     av.bytes = av.rawString.getBytes(StandardCharsets.UTF_8);
    388                     out.length = av.bytes.length;
    389                 }
    390             }
    391         }
    392 
    393         public void encodeContent(BerOutputStream out) {
    394             // stub to avoid wrong encoder usage
    395             throw new RuntimeException("AttributeValue encodeContent MUST NOT be invoked");
    396         }
    397 
    398         @Override public int getEncodedLength(BerOutputStream out) { //FIXME name
    399             AttributeValue av = (AttributeValue) out.content;
    400             if (av.encoded != null) {
    401                 return out.length;
    402             } else {
    403                 return super.getEncodedLength(out);
    404             }
    405         }
    406     };
    407 
    408     public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {
    409             ASN1Oid.getInstance(), attributeValue }) {
    410 
    411         @Override protected Object getDecodedObject(BerInputStream in) throws IOException {
    412             Object[] values = (Object[]) in.content;
    413             return new AttributeTypeAndValue((int[]) values[0], (AttributeValue) values[1]);
    414         }
    415 
    416         @Override protected void getValues(Object object, Object[] values) {
    417             AttributeTypeAndValue atav = (AttributeTypeAndValue) object;
    418             values[0] = atav.oid.getOid();
    419             values[1] = atav.value;
    420         }
    421     };
    422 
    423     /**
    424      * Returns known OID or null.
    425      */
    426     private static ObjectIdentifier getOID(int[] oid) {
    427         int index = hashIntArray(oid) % CAPACITY;
    428 
    429         // look for OID in the pool
    430         ObjectIdentifier[] list = KNOWN_OIDS[index];
    431         for (int i = 0; list[i] != null; i++) {
    432             if (Arrays.equals(oid, list[i].getOid())) {
    433                 return list[i];
    434             }
    435         }
    436         return null;
    437     }
    438 
    439     /**
    440      * Adds known OID to pool.
    441      * for static AttributeTypeAndValue initialization only
    442      */
    443     private static void addOID(ObjectIdentifier oid) {
    444         int[] newOid = oid.getOid();
    445         int index = hashIntArray(newOid) % CAPACITY;
    446 
    447         // look for OID in the pool
    448         ObjectIdentifier[] list = KNOWN_OIDS[index];
    449         int i = 0;
    450         for (; list[i] != null; i++) {
    451             // check wrong static initialization: no duplicate OIDs
    452             if (Arrays.equals(newOid, list[i].getOid())) {
    453                 throw new Error("ObjectIdentifier: invalid static initialization; " +
    454                         "duplicate OIDs: " + oid.getName() + " " + list[i].getName());
    455             }
    456         }
    457 
    458         // check : to avoid NPE
    459         if (i == (CAPACITY - 1)) {
    460             throw new Error("ObjectIdentifier: invalid static initialization; " +
    461                     "small OID pool capacity");
    462         }
    463         list[i] = oid;
    464     }
    465 
    466     /**
    467      * Returns hash for array of integers.
    468      */
    469     private static int hashIntArray(int[] oid) {
    470         int intHash = 0;
    471         for (int i = 0; i < oid.length && i < 4; i++) {
    472             intHash += oid[i] << (8 * i); //TODO what about to find better one?
    473         }
    474         return intHash & 0x7FFFFFFF; // only positive
    475     }
    476 }
    477