001    /**
002     *
003     *  Licensed to the Apache Software Foundation (ASF) under one or more
004     *  contributor license agreements.  See the NOTICE file distributed with
005     *  this work for additional information regarding copyright ownership.
006     *  The ASF licenses this file to You under the Apache License, Version 2.0
007     *  (the "License"); you may not use this file except in compliance with
008     *  the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    
019    package org.apache.geronimo.util.asn1.x509;
020    
021    import java.io.ByteArrayInputStream;
022    import java.io.IOException;
023    
024    import org.apache.geronimo.util.asn1.ASN1InputStream;
025    import org.apache.geronimo.util.asn1.DERObject;
026    import org.apache.geronimo.util.asn1.DERObjectIdentifier;
027    
028    /**
029     * It turns out that the number of standard ways the fields in a DN should be
030     * encoded into their ASN.1 counterparts is rapidly approaching the
031     * number of machines on the internet. By default the X509Name class
032     * will produce PrintableStrings if the field value will decode to that,
033     * next UTF8Strings if the field value will decode to that, and finally BMPStrings
034     * if 16 bit characters are required.
035     * <p>
036     * The way this is done is with a default encoder which is
037     * implemented as follows:
038     * <pre>
039     * public class X509DefaultEntryConverter
040     *     extends X509NameEntryConverter
041     * {
042     *     public DERObject getConvertedValue(
043     *         DERObjectIdentifier  oid,
044     *         String               value)
045     *     {
046     *         if (str.length() != 0 && str.charAt(0) == '#')
047     *         {
048     *             return convertHexEncoded(str, 1);
049     *         }
050     *         if (oid.equals(EmailAddress))
051     *         {
052     *             return new DERIA5String(str);
053     *         }
054     *         else if (canBePrintable(str))
055     *         {
056     *             return new DERPrintableString(str);
057     *         }
058     *         else if (canBeUTF8(str))
059     *         {
060     *             return new DERUTF8String(str);
061     *         }
062     *         else
063     *         {
064     *             return new DERBMPString(str);
065     *         }
066     *     }
067     * }
068     */
069    public abstract class X509NameEntryConverter
070    {
071        /**
072         * Convert an inline encoded hex string rendition of an ASN.1
073         * object back into its corresponding ASN.1 object.
074         *
075         * @param str the hex encoded object
076         * @param off the index at which the encoding starts
077         * @return the decoded object
078         */
079        protected DERObject convertHexEncoded(
080            String  str,
081            int     off)
082            throws IOException
083        {
084            str = str.toLowerCase();
085            byte[]    data = new byte[str.length() / 2];
086            for (int index = 0; index != data.length; index++)
087            {
088                char left = str.charAt((index * 2) + off);
089                char right = str.charAt((index * 2) + off + 1);
090    
091                if (left < 'a')
092                {
093                    data[index] = (byte)((left - '0') << 4);
094                }
095                else
096                {
097                    data[index] = (byte)((left - 'a' + 10) << 4);
098                }
099                if (right < 'a')
100                {
101                    data[index] |= (byte)(right - '0');
102                }
103                else
104                {
105                    data[index] |= (byte)(right - 'a' + 10);
106                }
107            }
108    
109            ASN1InputStream aIn = new ASN1InputStream(
110                                                new ByteArrayInputStream(data));
111    
112            return aIn.readObject();
113        }
114    
115        /**
116         * return true if the passed in String can be represented without
117         * loss as a PrintableString, false otherwise.
118         */
119        protected boolean canBePrintable(
120            String  str)
121        {
122            for (int i = str.length() - 1; i >= 0; i--)
123            {
124                char    ch = str.charAt(i);
125    
126                if (str.charAt(i) > 0x007f)
127                {
128                    return false;
129                }
130    
131                if ('a' <= ch && ch <= 'z')
132                {
133                    continue;
134                }
135    
136                if ('A' <= ch && ch <= 'Z')
137                {
138                    continue;
139                }
140    
141                if ('0' <= ch && ch <= '9')
142                {
143                    continue;
144                }
145    
146                switch (ch)
147                {
148                case ' ':
149                case '\'':
150                case '(':
151                case ')':
152                case '+':
153                case '-':
154                case '.':
155                case ':':
156                case '=':
157                case '?':
158                    continue;
159                }
160    
161                return false;
162            }
163    
164            return true;
165        }
166    
167        /**
168         * return true if the passed in String can be represented without
169         * loss as a UTF8String, false otherwise.
170         */
171        protected boolean canBeUTF8(
172            String  str)
173        {
174            for (int i = str.length() - 1; i >= 0; i--)
175            {
176                if (str.charAt(i) > 0x00ff)
177                {
178                    return false;
179                }
180            }
181    
182            return true;
183        }
184    
185        /**
186         * Convert the passed in String value into the appropriate ASN.1
187         * encoded object.
188         *
189         * @param oid the oid associated with the value in the DN.
190         * @param value the value of the particular DN component.
191         * @return the ASN.1 equivalent for the value.
192         */
193        public abstract DERObject getConvertedValue(DERObjectIdentifier oid, String value);
194    }