001package org.apache.commons.ssl.asn1;
002
003import java.io.ByteArrayOutputStream;
004import java.io.IOException;
005import java.io.OutputStream;
006import java.math.BigInteger;
007
008public class DERObjectIdentifier
009    extends ASN1Object {
010    String identifier;
011
012    /**
013     * return an OID from the passed in object
014     *
015     * @throws IllegalArgumentException if the object cannot be converted.
016     */
017    public static DERObjectIdentifier getInstance(
018        Object obj) {
019        if (obj == null || obj instanceof DERObjectIdentifier) {
020            return (DERObjectIdentifier) obj;
021        }
022
023        if (obj instanceof ASN1OctetString) {
024            return new DERObjectIdentifier(((ASN1OctetString) obj).getOctets());
025        }
026
027        if (obj instanceof ASN1TaggedObject) {
028            return getInstance(((ASN1TaggedObject) obj).getObject());
029        }
030
031        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
032    }
033
034    /**
035     * return an Object Identifier from a tagged object.
036     *
037     * @param obj      the tagged object holding the object we want
038     * @param explicit true if the object is meant to be explicitly
039     *                 tagged false otherwise.
040     * @throws IllegalArgumentException if the tagged object cannot
041     *                                  be converted.
042     */
043    public static DERObjectIdentifier getInstance(
044        ASN1TaggedObject obj,
045        boolean explicit) {
046        return getInstance(obj.getObject());
047    }
048
049
050    DERObjectIdentifier(
051        byte[] bytes) {
052        StringBuffer objId = new StringBuffer();
053        long value = 0;
054        BigInteger bigValue = null;
055        boolean first = true;
056
057        for (int i = 0; i != bytes.length; i++) {
058            int b = bytes[i] & 0xff;
059
060            if (value < 0x80000000000000L) {
061                value = value * 128 + (b & 0x7f);
062                if ((b & 0x80) == 0)             // end of number reached
063                {
064                    if (first) {
065                        switch ((int) value / 40) {
066                            case 0:
067                                objId.append('0');
068                                break;
069                            case 1:
070                                objId.append('1');
071                                value -= 40;
072                                break;
073                            default:
074                                objId.append('2');
075                                value -= 80;
076                        }
077                        first = false;
078                    }
079
080                    objId.append('.');
081                    objId.append(value);
082                    value = 0;
083                }
084            } else {
085                if (bigValue == null) {
086                    bigValue = BigInteger.valueOf(value);
087                }
088                bigValue = bigValue.shiftLeft(7);
089                bigValue = bigValue.or(BigInteger.valueOf(b & 0x7f));
090                if ((b & 0x80) == 0) {
091                    objId.append('.');
092                    objId.append(bigValue);
093                    bigValue = null;
094                    value = 0;
095                }
096            }
097        }
098
099        this.identifier = objId.toString();
100    }
101
102    public DERObjectIdentifier(
103        String identifier) {
104        if (!isValidIdentifier(identifier)) {
105            throw new IllegalArgumentException("string " + identifier + " not an OID");
106        }
107
108        this.identifier = identifier;
109    }
110
111    public String getId() {
112        return identifier;
113    }
114
115    private void writeField(
116        OutputStream out,
117        long fieldValue)
118        throws IOException {
119        if (fieldValue >= (1L << 7)) {
120            if (fieldValue >= (1L << 14)) {
121                if (fieldValue >= (1L << 21)) {
122                    if (fieldValue >= (1L << 28)) {
123                        if (fieldValue >= (1L << 35)) {
124                            if (fieldValue >= (1L << 42)) {
125                                if (fieldValue >= (1L << 49)) {
126                                    if (fieldValue >= (1L << 56)) {
127                                        out.write((int) (fieldValue >> 56) | 0x80);
128                                    }
129                                    out.write((int) (fieldValue >> 49) | 0x80);
130                                }
131                                out.write((int) (fieldValue >> 42) | 0x80);
132                            }
133                            out.write((int) (fieldValue >> 35) | 0x80);
134                        }
135                        out.write((int) (fieldValue >> 28) | 0x80);
136                    }
137                    out.write((int) (fieldValue >> 21) | 0x80);
138                }
139                out.write((int) (fieldValue >> 14) | 0x80);
140            }
141            out.write((int) (fieldValue >> 7) | 0x80);
142        }
143        out.write((int) fieldValue & 0x7f);
144    }
145
146    private void writeField(
147        OutputStream out,
148        BigInteger fieldValue)
149        throws IOException {
150        int byteCount = (fieldValue.bitLength() + 6) / 7;
151        if (byteCount == 0) {
152            out.write(0);
153        } else {
154            BigInteger tmpValue = fieldValue;
155            byte[] tmp = new byte[byteCount];
156            for (int i = byteCount - 1; i >= 0; i--) {
157                tmp[i] = (byte) ((tmpValue.intValue() & 0x7f) | 0x80);
158                tmpValue = tmpValue.shiftRight(7);
159            }
160            tmp[byteCount - 1] &= 0x7f;
161            out.write(tmp);
162        }
163
164    }
165
166    void encode(
167        DEROutputStream out)
168        throws IOException {
169        OIDTokenizer tok = new OIDTokenizer(identifier);
170        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
171        DEROutputStream dOut = new DEROutputStream(bOut);
172
173        writeField(bOut,
174            Integer.parseInt(tok.nextToken()) * 40
175            + Integer.parseInt(tok.nextToken()));
176
177        while (tok.hasMoreTokens()) {
178            String token = tok.nextToken();
179            if (token.length() < 18) {
180                writeField(bOut, Long.parseLong(token));
181            } else {
182                writeField(bOut, new BigInteger(token));
183            }
184        }
185
186        dOut.close();
187
188        byte[] bytes = bOut.toByteArray();
189
190        out.writeEncoded(OBJECT_IDENTIFIER, bytes);
191    }
192
193    public int hashCode() {
194        return identifier.hashCode();
195    }
196
197    boolean asn1Equals(
198        DERObject o) {
199        if (!(o instanceof DERObjectIdentifier)) {
200            return false;
201        }
202
203        return identifier.equals(((DERObjectIdentifier) o).identifier);
204    }
205
206    public String toString() {
207        return getId();
208    }
209
210    private static boolean isValidIdentifier(
211        String identifier) {
212        if (identifier.length() < 3
213            || identifier.charAt(1) != '.') {
214            return false;
215        }
216
217        char first = identifier.charAt(0);
218        if (first < '0' || first > '2') {
219            return false;
220        }
221
222        boolean periodAllowed = false;
223        for (int i = identifier.length() - 1; i >= 2; i--) {
224            char ch = identifier.charAt(i);
225
226            if ('0' <= ch && ch <= '9') {
227                periodAllowed = true;
228                continue;
229            }
230
231            if (ch == '.') {
232                if (!periodAllowed) {
233                    return false;
234                }
235
236                periodAllowed = false;
237                continue;
238            }
239
240            return false;
241        }
242
243        return periodAllowed;
244    }
245}