summaryrefslogtreecommitdiffstats
path: root/pki/base/util/src/netscape/security/x509/LdapV3DNStrConverter.java
blob: 52ff67017e7661e4a8e966e10234d1c48a5a30eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package netscape.security.x509;

import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;
import java.util.Vector;

import netscape.security.util.DerValue;
import netscape.security.util.ObjectIdentifier;

/**
 * A converter that converts Ldap v3 DN strings as specified in
 * draft-ietf-asid-ldapv3-dn-03.txt to a X500Name, RDN or AVA and
 * vice versa.
 * 
 * @see LdapDNStrConverter
 * @see X500Name
 * @see RDN
 * @see AVA
 * @see X500NameAttrMap
 * 
 * @author Lily Hsiao, Slava Galperin at Netscape Communications, Inc.
 */

public class LdapV3DNStrConverter extends LdapDNStrConverter {
    //
    // Constructors
    //

    /**
     * Constructs a LdapV3DNStrConverter using the global default
     * X500NameAttrMap and accept OIDs not in the default X500NameAttrMap.
     * 
     * @see X500NameAttrMap
     */
    public LdapV3DNStrConverter() {
        attrMap = X500NameAttrMap.getDefault();

        acceptUnknownOids = true;
    }

    /**
     * Constructs a LdapV3DNStrConverter using the specified X500NameAttrMap
     * and a boolean indicating whether to accept OIDs not listed in the
     * X500NameAttrMap.
     * 
     * @param attributeMap a X500NameAttrMap
     * @param doAcceptUnknownOids whether to convert unregistered OIDs
     *            (oids not in the X500NameAttrMap)
     * @see X500NameAttrMap
     */
    public LdapV3DNStrConverter(X500NameAttrMap attributeMap,
                boolean doAcceptUnknownOids) {
        attrMap = attributeMap;
        acceptUnknownOids = doAcceptUnknownOids;

    }

    //
    // public parsing methods
    // From LdapDNStrConverter interface
    //

    /**
     * Parse a Ldap v3 DN string to a X500Name.
     * 
     * @param dn a LDAP v3 DN String
     * @return a X500Name
     * @exception IOException if an error occurs during the conversion.
     */
    public X500Name parseDN(String dn)
            throws IOException {
        return parseDN(dn, null);
    }

    /**
     * Like parseDN(String) with a DER encoding order given as argument for
     * Directory Strings.
     */
    public X500Name parseDN(String dn, byte[] encodingOrder)
            throws IOException {
        StringReader dn_reader = new StringReader(dn);
        PushbackReader in = new PushbackReader(dn_reader, 5);
        Vector<RDN> rdnVector = new Vector<RDN>();
        RDN[] names;

        return parseDN(in, encodingOrder);
    }

    /**
     * Parse a Ldap v3 DN string with a RDN component to a RDN
     * 
     * @param rdn a LDAP v3 DN String
     * @return a RDN
     * @exception IOException if an error occurs during the conversion.
     */
    public RDN parseRDN(String rdn)
            throws IOException {
        return parseRDN(rdn, null);
    }

    /**
     * Like parseRDN(String) with a DER encoding order given as argument for
     * Directory Strings.
     */
    public RDN parseRDN(String rdn, byte[] encodingOrder)
            throws IOException {
        StringReader rdn_reader = new StringReader(rdn);
        PushbackReader in = new PushbackReader(rdn_reader, 5);

        return parseRDN(in, null);
    }

    /**
     * Parse a Ldap v3 DN string with a AVA component to a AVA.
     * 
     * @param ava a LDAP v3 DN string
     * @return a AVA
     */
    public AVA parseAVA(String ava)
            throws IOException {
        return parseAVA(ava, null);
    }

    /**
     * Like parseDN(String) with a DER encoding order given as argument for
     * Directory Strings.
     */
    public AVA parseAVA(String ava, byte[] encodingOrder)
            throws IOException {
        StringReader ava_reader = new StringReader(ava);
        PushbackReader in = new PushbackReader(ava_reader, 5);

        return parseAVA(in, encodingOrder);
    }

    //
    // public parsing methods called by other methods.
    //

    /**
     * Parses a Ldap DN string in a string reader to a X500Name.
     * 
     * @param in Pushback string reader for a Ldap DN string.
     *            The pushback reader must have a pushback buffer size > 2.
     * 
     * @return a X500Name
     * 
     * @exception IOException if any reading or parsing error occurs.
     */
    public X500Name parseDN(PushbackReader in)
            throws IOException {
        return parseDN(in, null);
    }

    /**
     * Like parseDN(PushbackReader in) with a DER encoding order given as
     * argument for Directory Strings.
     */
    public X500Name parseDN(PushbackReader in, byte[] encodingOrder)
            throws IOException {
        RDN rdn;
        int lastChar;
        Vector<RDN> rdnVector = new Vector<RDN>();
        RDN names[];
        int i, j;

        do {
            rdn = parseRDN(in, encodingOrder);
            rdnVector.addElement(rdn);
            lastChar = in.read();
        } while (lastChar == ',' || lastChar == ';');

        names = new RDN[rdnVector.size()];
        for (i = 0, j = rdnVector.size() - 1; i < rdnVector.size(); i++, j--)
            names[j] = (RDN) rdnVector.elementAt(i);
        return new X500Name(names);
    }

    /**
     * Parses Ldap DN string with a rdn component
     * from a string reader to a RDN. The string reader will point
     * to the separator after the rdn component or -1 if at end of string.
     * 
     * @param in Pushback string reader containing a Ldap DN string with
     *            at least one rdn component.
     *            The pushback reader must have a pushback buffer size > 2.
     * 
     * @return RDN object of the first rdn component in the Ldap DN string.
     * 
     * @exception IOException if any read or parse error occurs.
     */
    public RDN parseRDN(PushbackReader in)
            throws IOException {
        return parseRDN(in, null);
    }

    /**
     * Like parseRDN(PushbackReader) with a DER encoding order given as
     * argument for Directory Strings.
     */
    public RDN parseRDN(PushbackReader in, byte[] encodingOrder)
            throws IOException {
        Vector<AVA> avaVector = new Vector<AVA>();
        AVA ava;
        int lastChar;
        AVA assertion[];

        do {
            ava = parseAVA(in, encodingOrder);
            avaVector.addElement(ava);
            lastChar = in.read();
        } while (lastChar == '+');

        if (lastChar != -1)
            in.unread(lastChar);

        assertion = new AVA[avaVector.size()];
        for (int i = 0; i < avaVector.size(); i++)
            assertion[i] = (AVA) avaVector.elementAt(i);
        return new RDN(assertion);
    }

    /**
     * Parses a Ldap DN string with a AVA component
     * from a string reader to an AVA. The string reader will point
     * to the AVA separator after the ava string or -1 if end of string.
     * 
     * @param in a Pushback reader containg a Ldap string with
     *            at least one AVA component.
     *            The Pushback reader must have a pushback buffer size > 2.
     * 
     * @return AVA object of the first AVA component in the Ldap DN string.
     */
    public AVA parseAVA(PushbackReader in)
            throws IOException {
        return parseAVA(in, null);
    }

    /**
     * Like parseAVA(PushbackReader) with a DER encoding order given as
     * argument for Directory Strings.
     */
    public AVA parseAVA(PushbackReader in, byte[] encodingOrder)
            throws IOException {
        int c;
        ObjectIdentifier oid;
        DerValue value;
        StringBuffer keywordBuf;
        StringBuffer valueBuf;
        ByteArrayOutputStream berStream;
        char hexChar1, hexChar2;
        CharArrayWriter hexCharsBuf;
        String endChars;

        /* First get the keyword indicating the attribute's type,
         * and map it to the appropriate OID.
         */
        keywordBuf = new StringBuffer();
        for (;;) {
            c = in.read();
            if (c == '=')
                break;
            if (c == -1) {
                throw new IOException("Bad AVA format: Missing '='");
            }
            keywordBuf.append((char) c);
        }
        oid = parseAVAKeyword(keywordBuf.toString());

        /* Now parse the value.  "#hex", a quoted string, or a string
             * terminated by "+", ",", ";", ">".  Whitespace before or after
             * the value is stripped.
             */
        for (c = in.read(); c == ' '; c = in.read())
            continue;
        if (c == -1)
            throw new IOException("Bad AVA format: Missing attribute value");

        if (c == '#') {
            /*
             * NOTE per LDAPv3 dn string ietf standard the value represented
             * by this form is a BER value. But we only support DER value here
             * which is only a form of BER.
             */
            berStream = new ByteArrayOutputStream();
            int b;
            for (;;) {
                hexChar1 = (char) (c = in.read());
                if (c == -1 || octoEndChars.indexOf(c) > 0) // end of value
                    break;
                hexChar2 = (char) (c = in.read());
                if (hexDigits.indexOf(hexChar1) == -1 ||
                        hexDigits.indexOf(hexChar2) == -1)
                    throw new IOException("Bad AVA value: bad hex value.");
                b = (Character.digit(hexChar1, 16) << 4) +
                        Character.digit(hexChar2, 16);
                berStream.write(b);
            }
            if (berStream.size() == 0)
                throw new IOException("bad AVA format: invalid hex value");

            value = parseAVAValue(berStream.toByteArray(), oid);

            while (c == ' ' && c != -1)
                c = in.read();
        } else {
            valueBuf = new StringBuffer();
            boolean quoted = false;
            if (c == '"') {
                quoted = true;
                endChars = quotedEndChars;
                if ((c = in.read()) == -1)
                    throw new IOException("Bad AVA format: Missing attrValue");
            } else {
                endChars = valueEndChars;
            }

            // QUOTATION * ( quotechar / pair ) QUOTATION
            // quotechar = any character except '\' or QUOTATION
            // pair = '\' ( special | '\' | QUOTATION | hexpair )
            while (c != -1 && endChars.indexOf(c) == -1) {
                if (c == '\\') {
                    if ((c = in.read()) == -1)
                        throw new IOException("Bad AVA format: expecting " +
                                              "escaped char.");
                    // expect escaping of special chars, space and CR.
                    if (specialChars.indexOf((char) c) != -1 || c == '\n' ||
                            c == '\\' || c == '"' || c == ' ') {
                        valueBuf.append((char) c);
                    } else if (hexDigits.indexOf(c) != -1) {
                        hexCharsBuf = new CharArrayWriter();
                        // handle sequence of '\' hexpair
                        do {
                            hexChar1 = (char) c;
                            hexChar2 = (char) (c = in.read());
                            if (hexDigits.indexOf((char) c) == -1)
                                throw new IOException("Bad AVA format: " +
                                        "invalid escaped hex pair");
                            hexCharsBuf.write(hexChar1);
                            hexCharsBuf.write(hexChar2);
                            // read ahead to next '\' hex-char if any.
                            if ((c = in.read()) == -1)
                                break;
                            if (c != '\\') {
                                in.unread(c);
                                break;
                            }
                            if ((c = in.read()) == -1)
                                throw new IOException("Bad AVA format: " +
                                        "expecting escaped char.");
                            if (hexDigits.indexOf((char) c) == -1) {
                                in.unread(c);
                                in.unread((int) '\\');
                                break;
                            }
                        } while (true);
                        valueBuf.append(
                                getStringFromHexpairs(hexCharsBuf.toCharArray()));
                    } else {
                        throw new IOException("Bad AVA format: " +
                                              "invalid escaping");
                    }
                } else
                    valueBuf.append((char) c);
                c = in.read();
            }

            value = parseAVAValue(
                    valueBuf.toString().trim(), oid, encodingOrder);

            if (quoted) { // move to next non-white space
                do {
                    c = in.read();
                } while (c == ' ');
                if (c != -1 && valueEndChars.indexOf(c) == -1)
                    throw new IOException(
                            "Bad AVA format: separator expected at end of ava.");
            }
        }

        if (c != -1)
            in.unread(c);

        return new AVA(oid, value);
    }

    /**
     * Converts a AVA keyword from a Ldap DN string to an ObjectIdentifier
     * from the attribute map or, if this keyword is an OID not
     * in the attribute map, create a new ObjectIdentifier for the keyword
     * if acceptUnknownOids is true.
     * 
     * @param avaKeyword AVA keyword from a Ldap DN string.
     * 
     * @return a ObjectIdentifier object
     * @exception IOException if the keyword is an OID not in the attribute
     *                map and acceptUnknownOids is false, or
     *                if an error occurs during conversion.
     */
    public ObjectIdentifier parseAVAKeyword(String avaKeyword)
            throws IOException {
        String keyword = avaKeyword.toUpperCase().trim();
        String oid_str = null;
        ObjectIdentifier oid, new_oid;

        if (Character.digit(keyword.charAt(0), 10) != -1) {
            // value is an oid string of 1.2.3.4
            oid_str = keyword;
        } else if (keyword.startsWith("oid.") || keyword.startsWith("OID.")) {
            // value is an oid string of oid.1.2.3.4 or OID.1.2...
            oid_str = keyword.substring(4);
        }

        if (oid_str != null) {
            // value is an oid string of 1.2.3.4 or oid.1.2.3.4 or OID.1.2...
            new_oid = new ObjectIdentifier(oid_str);
            oid = attrMap.getOid(new_oid);
            if (oid == null) {
                if (!acceptUnknownOids)
                    throw new IOException("Unknown AVA OID.");
                oid = new_oid;
            }
        } else {
            oid = attrMap.getOid(keyword);
            if (oid == null)
                throw new IOException("Unknown AVA keyword '" + keyword + "'.");
        }

        return oid;
    }

    /**
     * Converts a AVA value from a Ldap dn string to a
     * DerValue according the attribute type. For example, a value for
     * CN, OU or O is expected to be a Directory String and will be converted
     * to a DerValue of ASN.1 type PrintableString, T61String or
     * UniversalString. A Directory String is a ASN.1 CHOICE of Printable,
     * T.61 or Universal string.
     * 
     * @param avaValueString a attribute value from a Ldap DN string.
     * @param oid OID of the attribute.
     * 
     * @return DerValue for the value.
     * 
     * @exception IOException if an error occurs during conversion.
     * @see AVAValueConverter
     */
    public DerValue parseAVAValue(String avaValueString, ObjectIdentifier oid)
            throws IOException {
        return parseAVAValue(avaValueString, oid, null);
    }

    /**
     * Like parseAVAValue(String) with a DER encoding order given as argument
     * for Directory Strings.
     */
    public DerValue parseAVAValue(
            String avaValueString, ObjectIdentifier oid, byte[] encodingOrder)
            throws IOException {
        AVAValueConverter valueConverter = attrMap.getValueConverter(oid);
        if (valueConverter == null) {
            if (!acceptUnknownOids) {
                throw new IllegalArgumentException(
                        "Unrecognized OID for AVA value conversion");
            } else {
                valueConverter = new GenericValueConverter();
            }
        }
        return valueConverter.getValue(avaValueString, encodingOrder);
    }

    /**
     * Converts a value in BER encoding, for example given in octothorpe form
     * in a Ldap v3 dn string, to a DerValue. Checks if the BER encoded value
     * is a legal value for the attribute.
     * <p>
     * <strong><i>NOTE:</i></strong> only DER encoded values are supported for the BER encoded value.
     * 
     * @param berValue a value in BER encoding
     * @param oid ObjectIdentifier of the attribute.
     * 
     * @return DerValue for the BER encoded value
     * @exception IOException if an error occurs during conversion.
     */
    public DerValue parseAVAValue(byte[] berValue, ObjectIdentifier oid)
            throws IOException {
        AVAValueConverter valueConverter = attrMap.getValueConverter(oid);
        if (valueConverter == null && !acceptUnknownOids) {
            throw new IllegalArgumentException(
                    "Unrecognized OID for AVA value conversion");
        } else {
            valueConverter = new GenericValueConverter();
        }
        return valueConverter.getValue(berValue);
    }

    //
    // public encoding methods.
    //

    /**
     * Converts a X500Name object to a Ldap v3 DN string (except in unicode).
     * 
     * @param x500name a X500Name
     * 
     * @return a Ldap v3 DN String (except in unicode).
     * 
     * @exception IOException if an error is encountered during conversion.
     */
    public String encodeDN(X500Name x500name)
            throws IOException {
        RDN[] rdns = x500name.getNames();
        // String fullname = null;
        StringBuffer fullname = new StringBuffer();
        String s;
        int i;
        if (rdns.length == 0)
            return "";
        i = rdns.length - 1;
        fullname.append(encodeRDN(rdns[i--]));
        while (i >= 0) {
            s = encodeRDN(rdns[i--]);
            fullname.append(",");
            fullname.append(s);
        }
        ;
        return fullname.toString();
    }

    /**
     * Converts a RDN to a Ldap v3 DN string (except in unicode).
     * 
     * @param rdn a RDN
     * 
     * @return a LDAP v3 DN string (except in unicode).
     * 
     * @exception IOException if an error is encountered during conversion.
     */
    public String encodeRDN(RDN rdn)
            throws IOException {
        AVA[] avas = rdn.getAssertion();
        // String relname = null;
        StringBuffer relname = new StringBuffer();
        String s;
        int i = 0;

        relname.append(encodeAVA(avas[i++]));
        while (i < avas.length) {
            s = encodeAVA(avas[i++]);
            relname.append("+");
            relname.append(s);
        }
        ;
        return relname.toString();
    }

    /**
     * Converts a AVA to a Ldap v3 DN String (except in unicode).
     * 
     * @param ava an AVA
     * 
     * @return a Ldap v3 DN string (except in unicode).
     * 
     * @exception IOException If an error is encountered during exception.
     */
    public String encodeAVA(AVA ava)
            throws IOException {
        if (ava == null) {
            return "";
        }
        ObjectIdentifier oid = ava.getOid();
        DerValue value = ava.getValue();
        String keyword, valueStr;

        // get attribute name

        keyword = encodeOID(oid);
        valueStr = encodeValue(value, oid);

        return keyword + "=" + valueStr;
    }

    /**
     * Converts an OID to a attribute keyword in a Ldap v3 DN string
     * - either a keyword if known or a string of "1.2.3.4" syntax.
     * 
     * @param oid a ObjectIdentifier
     * 
     * @return a keyword to use in a Ldap V3 DN string.
     * 
     * @exception IOException if an error is encountered during conversion.
     */
    public String encodeOID(ObjectIdentifier oid)
            throws IOException {
        String keyword = attrMap.getName(oid);
        if (keyword == null) {
            if (acceptUnknownOids)
                keyword = oid.toString();
            else
                throw new IOException("Unknown OID");
        }
        return keyword;
    }

    /**
     * Converts a value as a DerValue to a string in a Ldap V3 DN String.
     * If the value cannot be converted to a string it will be encoded in
     * octothorpe form.
     * 
     * @param attrValue a value as a DerValue.
     * @param oid OID for the attribute.
     * @return a string for the value in a LDAP v3 DN String
     * @exception IOException if an error occurs during conversion.
     */
    public String encodeValue(DerValue attrValue, ObjectIdentifier oid)
            throws IOException {
        /*
         * Construct the value with as little copying and garbage
         * production as practical.
         */
        StringBuffer retval = new StringBuffer(30);
        int i;
        String temp = null;
        AVAValueConverter valueConverter;

        X500NameAttrMap lAttrMap = attrMap;

        if (attrValue.tag == DerValue.tag_UTF8String) {
            lAttrMap = X500NameAttrMap.getDirDefault();

        }

        valueConverter = lAttrMap.getValueConverter(oid);
        if (valueConverter == null) {
            if (acceptUnknownOids)
                valueConverter = new GenericValueConverter();
            else
                throw new IOException(
                        "Unknown AVA type for encoding AVA value");
        }

        try {
            temp = valueConverter.getAsString(attrValue);

            if (temp == null) {
                // convert to octothorpe form.
                byte data[] = attrValue.toByteArray();

                retval.append('#');
                for (i = 0; i < data.length; i++) {
                    retval.append(hexDigits.charAt((data[i] >> 4) & 0x0f));
                    retval.append(hexDigits.charAt(data[i] & 0x0f));
                }

            } else {

                retval.append(encodeString(temp));

            }
        } catch (IOException e) {
            throw new IllegalArgumentException("malformed AVA DER Value");
        }

        return retval.toString();
    }

    /**
     * converts a raw value string to a string in Ldap V3 DN string format.
     * 
     * @param valueStr a 'raw' value string.
     * @return a attribute value string in Ldap V3 DN string format.
     */
    public String encodeString(String valueStr) {
        int i, j;
        int len;
        StringBuffer retval = new StringBuffer();

        /*
         * generate string according to ldapv3 DN. escaping is used.
         * Strings generated this way are acceptable by rfc1779
         * implementations.
         */
        len = valueStr.length();

        // get index of first space at the end of the string.
        for (j = len - 1; j >= 0 && valueStr.charAt(j) == ' '; j--)
            continue;

        // escape spaces at the beginning of the string.
        for (i = 0; i <= j && valueStr.charAt(i) == ' '; i++) {
            retval.append('\\');
            retval.append(valueStr.charAt(i));
        }

        // escape special characters in the middle of the string.
        for (; i <= j; i++) {
            if (valueStr.charAt(i) == '\\') {
                retval.append('\\');
                retval.append(valueStr.charAt(i));
            } else if (specialChars.indexOf(valueStr.charAt(i)) != -1) {
                retval.append('\\');
                retval.append(valueStr.charAt(i));
            } else if (valueStr.charAt(i) == '"') {
                retval.append('\\');
                retval.append(valueStr.charAt(i));
            } else
                retval.append(valueStr.charAt(i));
        }

        // esacape spaces at the end.
        for (; i < valueStr.length(); i++) {
            retval.append('\\');
            retval.append(' ');
        }

        return retval.toString();
    }

    //
    // public get/set methods
    //

    /**
     * gets the X500NameAttrMap used by the converter.
     * 
     * @return X500NameAttrMap used by this converter.
     */
    public X500NameAttrMap getAttrMap() {
        return attrMap;
    }

    /**
     * returns true if the converter accepts unregistered attributes i.e.
     * OIDS not in the X500NameAttrMap.
     * 
     * @return true if converter converts attributes not in the
     *         X500NameAttrMap.
     */
    public boolean getAcceptUnknownOids() {
        return acceptUnknownOids;
    }

    //
    // private and protected variables
    //

    protected X500NameAttrMap attrMap;
    protected boolean acceptUnknownOids;

    //
    // private and protected static variables & methods.
    //

    protected static final String specialChars = ",+=<>#;";

    protected static final String valueEndChars = "+,;>";
    protected static final String quotedEndChars = "\"";
    protected static final String octoEndChars = " " + valueEndChars;

    /*
     * Values that aren't printable strings are emitted as BER-encoded
     * hex data.
     */
    protected static final String hexDigits = "0123456789ABCDEFabcdef";

    /**
     * Parse a sequence of hex pairs, each pair a UTF8 byte to a java string.
     * For example, "4C75C48D" is "Luc", the last c with caron.
     */
    protected static char[] getStringFromHexpairs(char[] hexPairs) throws UnsupportedEncodingException {
        try {
            byte[] buffer = new byte[hexPairs.length / 2];

            for (int i = 0; i < buffer.length; i++) {
                buffer[i] = (byte)
                        ((Character.digit(hexPairs[i * 2], 16) << 4) +
                        Character.digit(hexPairs[i * 2 + 1], 16));
            }

            Charset charset = Charset.forName("UTF-8");
            CharsetDecoder decoder = charset.newDecoder();

            CharBuffer charBuffer = decoder.decode(ByteBuffer.wrap(buffer));

            return Arrays.copyOfRange(charBuffer.array(),
                    charBuffer.arrayOffset(), charBuffer.arrayOffset() + charBuffer.limit());

        } catch (UnsupportedCharsetException e) {
            throw new UnsupportedEncodingException(
                    "No UTF8 byte to char converter to use for " +
                            "parsing LDAP DN String");

        } catch (CharacterCodingException e) {
            throw new IllegalArgumentException(
                    "Invalid hex pair in LDAP DN String.");
        }
    }
}