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