]> sjero.net Git - linphone/blob - p2pproxy/dependencies-src/jxse-src-2.5/impl/src/net/jxta/impl/membership/pse/PSEUtils.java
9e923a7b7024ab85867eeac23ad2a0ae6a856811
[linphone] / p2pproxy / dependencies-src / jxse-src-2.5 / impl / src / net / jxta / impl / membership / pse / PSEUtils.java
1 /*
2  * Copyright (c) 2001-2007 Sun Microsystems, Inc.  All rights reserved.
3  *  
4  *  The Sun Project JXTA(TM) Software License
5  *  
6  *  Redistribution and use in source and binary forms, with or without 
7  *  modification, are permitted provided that the following conditions are met:
8  *  
9  *  1. Redistributions of source code must retain the above copyright notice,
10  *     this list of conditions and the following disclaimer.
11  *  
12  *  2. Redistributions in binary form must reproduce the above copyright notice, 
13  *     this list of conditions and the following disclaimer in the documentation 
14  *     and/or other materials provided with the distribution.
15  *  
16  *  3. The end-user documentation included with the redistribution, if any, must 
17  *     include the following acknowledgment: "This product includes software 
18  *     developed by Sun Microsystems, Inc. for JXTA(TM) technology." 
19  *     Alternately, this acknowledgment may appear in the software itself, if 
20  *     and wherever such third-party acknowledgments normally appear.
21  *  
22  *  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must 
23  *     not be used to endorse or promote products derived from this software 
24  *     without prior written permission. For written permission, please contact 
25  *     Project JXTA at http://www.jxta.org.
26  *  
27  *  5. Products derived from this software may not be called "JXTA", nor may 
28  *     "JXTA" appear in their name, without prior written permission of Sun.
29  *  
30  *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
31  *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
32  *  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN 
33  *  MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
34  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
35  *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
36  *  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
37  *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
38  *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
39  *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40  *  
41  *  JXTA is a registered trademark of Sun Microsystems, Inc. in the United 
42  *  States and other countries.
43  *  
44  *  Please see the license information page at :
45  *  <http://www.jxta.org/project/www/license.html> for instructions on use of 
46  *  the license in source files.
47  *  
48  *  ====================================================================
49  *  
50  *  This software consists of voluntary contributions made by many individuals 
51  *  on behalf of Project JXTA. For more information on Project JXTA, please see 
52  *  http://www.jxta.org.
53  *  
54  *  This license is based on the BSD license adopted by the Apache Foundation. 
55  */
56
57 package net.jxta.impl.membership.pse;
58
59
60 import net.jxta.impl.util.BASE64InputStream;
61 import net.jxta.impl.util.BASE64OutputStream;
62 import net.jxta.logging.Logging;
63 import org.bouncycastle.asn1.x509.X509NameTokenizer;
64 import org.bouncycastle.asn1.DERObjectIdentifier;
65 import org.bouncycastle.jce.X509Principal;
66 import org.bouncycastle.jce.provider.BouncyCastleProvider;
67 import org.bouncycastle.x509.X509V3CertificateGenerator;
68
69 import javax.crypto.Cipher;
70 import javax.crypto.EncryptedPrivateKeyInfo;
71 import javax.crypto.SecretKey;
72 import javax.crypto.SecretKeyFactory;
73 import javax.crypto.spec.PBEKeySpec;
74 import javax.crypto.spec.PBEParameterSpec;
75 import javax.security.auth.x500.X500Principal;
76 import java.io.BufferedReader;
77 import java.io.BufferedWriter;
78 import java.io.ByteArrayOutputStream;
79 import java.io.IOException;
80 import java.io.InputStream;
81 import java.io.Reader;
82 import java.io.StringReader;
83 import java.io.StringWriter;
84 import java.math.BigInteger;
85 import java.security.AlgorithmParameters;
86 import java.security.InvalidKeyException;
87 import java.security.KeyFactory;
88 import java.security.KeyPair;
89 import java.security.KeyPairGenerator;
90 import java.security.MessageDigest;
91 import java.security.NoSuchAlgorithmException;
92 import java.security.PrivateKey;
93 import java.security.Provider;
94 import java.security.SecureRandom;
95 import java.security.Security;
96 import java.security.Signature;
97 import java.security.SignatureException;
98 import java.security.cert.Certificate;
99 import java.security.cert.X509Certificate;
100 import java.security.spec.InvalidKeySpecException;
101 import java.security.spec.KeySpec;
102 import java.util.Calendar;
103 import java.util.Date;
104 import java.util.Hashtable;
105 import java.util.logging.Level;
106 import java.util.logging.Logger;
107
108
109 /**
110  * Singleton class of static utility methods.
111  */
112 public final class PSEUtils {
113
114     /**
115      * Logger
116      */
117     private static final transient Logger LOG = Logger.getLogger(PSEUtils.class.getName());
118
119     /**
120      * Singleton instance.
121      */
122     private static final PSEUtils UTILS = new PSEUtils();
123
124     /**
125      * A SecureRandom for generating keys.
126      */
127     final transient SecureRandom srng = new SecureRandom();
128
129     /**
130      * Singleton utility class
131      */
132     private PSEUtils() {
133
134         try {
135             ClassLoader sysloader = ClassLoader.getSystemClassLoader();
136
137             Class<?> loaded = sysloader.loadClass(BouncyCastleProvider.class.getName());
138
139             Provider provider = (Provider) loaded.newInstance();
140
141             Security.addProvider(provider);
142
143             if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
144                 LOG.info("Loaded Security Providers into system class loader");
145             }
146         } catch (Exception disallowed) {
147             if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
148                 LOG.log(Level.WARNING,
149                         "Failed loading Security Providers into System Class Loader. Will try local class loader (which may not work)",
150                         disallowed);
151             }
152
153             // Add the providers we use.
154             Security.addProvider(new BouncyCastleProvider());
155
156             if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
157                 LOG.info("Loaded Security Providers into local class loader");
158             }
159         }
160
161         // Provider [] providers = Security.getProviders();
162         // Iterator eachProvider = Arrays.asList(providers).iterator();
163         //
164         // while (eachProvider.hasNext()) {
165         // Provider aProvider = (Provider) eachProvider.next();
166         //
167         // System.out.println("\n\n" + aProvider.getName() + " - " + aProvider.getVersion() + " - " + aProvider.getInfo());
168         //
169         // Iterator allMappings = aProvider.entrySet().iterator();
170         //
171         // while (allMappings.hasNext()) {
172         // Map.Entry aMapping = (Map.Entry) allMappings.next();
173         //
174         // Object key = aMapping.getKey();
175         // System.out.println(key + " (" + key.getClass().getName() + ") --> " + aMapping.getValue() + " (" + key.getClass().getName() + ")");
176         // }
177         // }
178     }
179
180     /**
181      * Issuer Information
182      */
183     public static class IssuerInfo {
184         public X509Certificate cert; // subject Cert
185         public PrivateKey subjectPkey; // subject private key
186         public X509Certificate issuer; // issuer Cert
187         public PrivateKey issuerPkey; // issuer private key
188     }
189
190     /**
191      * Generate a Cert
192      *
193      * @param cn         subject cn for the certificate
194      * @param issuerinfo the cert issuer or null if self-signed root cert.
195      * @return the details of the generated cert.
196      * @throws SecurityException if the cert could not be generated.
197      */
198     public static IssuerInfo genCert(String cn, IssuerInfo issuerinfo) throws SecurityException {
199         try {
200             String useCN;
201
202             if (null == issuerinfo) {
203                 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
204                     LOG.fine("Generating Self Signed Cert ...");
205                 }
206
207                 if (!cn.endsWith("-CA")) {
208                     useCN = cn + "-CA";
209                 } else {
210                     useCN = cn;
211                 }
212             } else {
213                 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
214                     LOG.fine("Generating Client Cert ...");
215                 }
216
217                 useCN = cn;
218             }
219
220             // set name attribute
221             Hashtable<DERObjectIdentifier, String> attrs = new Hashtable<DERObjectIdentifier, String>();
222
223             attrs.put(X509Principal.CN, useCN);
224             attrs.put(X509Principal.O, "www.jxta.org");
225
226             // XXX bondolo 20040405 wouldn't SN or UID be a better choice?
227             // set ou to 20 random digits
228             byte[] ou = new byte[10];
229
230             UTILS.srng.nextBytes(ou);
231             String ouStr = toHexDigits(ou);
232
233             attrs.put(X509Principal.OU, ouStr);
234
235             X509Principal subject = new X509Principal(attrs);
236             X500Principal samesubject = new X500Principal(subject.getEncoded());
237             KeyPairGenerator g = KeyPairGenerator.getInstance("RSA");
238
239             g.initialize(1024, UTILS.srng);
240
241             KeyPair keypair = g.generateKeyPair();
242
243             return genCert(samesubject, keypair, issuerinfo);
244         } catch (NoSuchAlgorithmException e) {
245             if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
246                 LOG.log(Level.SEVERE, "Could not generate certificate", e);
247             }
248             SecurityException failure = new SecurityException("Could not generate certificate");
249
250             failure.initCause(e);
251             throw failure;
252         }
253     }
254
255     /**
256      * Generate a Cert given a keypair
257      *
258      * @param subject    subjectDN for the certificate
259      * @param keypair    the keypair to use.
260      * @param issuerinfo the cert issuer or null if self-signed root cert.
261      * @return the details of the generated cert.
262      * @throws SecurityException if the cert could not be generated.
263      */
264     public static IssuerInfo genCert(X500Principal subject, KeyPair keypair, IssuerInfo issuerinfo) throws SecurityException {
265         try {
266             // set up issuer
267             PrivateKey signer;
268             X509Principal issuer;
269
270             if (null == issuerinfo) { // self-signed root cert
271                 signer = keypair.getPrivate();
272                 issuer = new X509Principal(subject.getEncoded());
273             } else { // issuer signed service sert
274                 signer = issuerinfo.subjectPkey;
275                 X500Principal issuer_subject = issuerinfo.cert.getSubjectX500Principal();
276
277                 issuer = new X509Principal(issuer_subject.getEncoded());
278             }
279
280             // set validity 10 years from today
281             Date today = new Date();
282             Calendar cal = Calendar.getInstance();
283
284             cal.setTime(today);
285             cal.add(Calendar.YEAR, 10);
286             Date until = cal.getTime();
287
288             // generate cert
289             X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
290
291             certGen.setIssuerDN(issuer);
292             certGen.setSubjectDN(new X509Principal(subject.getEncoded()));
293             certGen.setNotBefore(today);
294             certGen.setNotAfter(until);
295             certGen.setPublicKey(keypair.getPublic());
296             // certGen.setSignatureAlgorithm("SHA1withDSA");
297             certGen.setSignatureAlgorithm("SHA1WITHRSA");
298             // FIXME bondolo 20040317 needs fixing.
299             certGen.setSerialNumber(BigInteger.valueOf(1));
300
301             // return issuer info for generating service cert
302             IssuerInfo info = new IssuerInfo();
303
304             // the cert
305             info.cert = certGen.generateX509Certificate(signer, UTILS.srng);
306
307             // For saving service cert private key
308             info.subjectPkey = keypair.getPrivate();
309
310             // for signing service cert
311             info.issuer = (null == issuerinfo) ? info.cert : issuerinfo.cert;
312
313             // for signing service cert
314             info.issuerPkey = signer;
315
316             // dump the certificate?
317             if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
318                 if (null == issuer) {
319                     LOG.fine("Root Cert : \n" + info.cert.toString());
320                 } else {
321                     LOG.fine("Client Cert : \n" + info.cert.toString());
322                 }
323             }
324
325             return info;
326         } catch (SignatureException e) {
327             if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
328                 LOG.log(Level.FINE, "Could not generate certificate", e);
329             }
330
331             SecurityException failure = new SecurityException("Could not generate certificate");
332
333             failure.initCause(e);
334             throw failure;
335         } catch (InvalidKeyException e) {
336             if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
337                 LOG.log(Level.FINE, "Could not generate certificate", e);
338             }
339
340             SecurityException failure = new SecurityException("Could not generate certificate");
341
342             failure.initCause(e);
343             throw failure;
344         } catch (IOException e) {
345             if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
346                 LOG.log(Level.FINE, "Could not generate certificate", e);
347             }
348
349             SecurityException failure = new SecurityException("Could not generate certificate");
350
351             failure.initCause(e);
352             throw failure;
353         }
354     }
355
356     /**
357      * return the CN token from the provided cert's subjectDN
358      *
359      * @param cert the certificate to examine
360      * @return the CN name or null if none could be found.
361      */
362     public static String getCertSubjectCName(X509Certificate cert) {
363
364         // get the subject dname
365         X500Principal subject = cert.getSubjectX500Principal();
366
367         X509NameTokenizer tokens = new X509NameTokenizer(subject.getName());
368
369         // iterate over the attributes of the dname
370         while (tokens.hasMoreTokens()) {
371             String aToken = tokens.nextToken();
372
373             if (aToken.length() < 3) {
374                 continue;
375             }
376
377             String attribute = aToken.substring(0, 3);
378
379             if ("CN=".equalsIgnoreCase(attribute)) {
380                 return aToken.substring(3);
381             }
382         }
383
384         return null;
385     }
386
387     /**
388      * return the CN token from the provided cert's issuerDN
389      *
390      * @param cert the certificate to examine
391      * @return the CN name or null if none could be found.
392      */
393     public static String getCertIssuerCName(X509Certificate cert) {
394
395         // get the subject dname
396         X500Principal issuer = cert.getIssuerX500Principal();
397
398         X509NameTokenizer tokens = new X509NameTokenizer(issuer.getName());
399
400         // iterate over the attributes of the dname
401         while (tokens.hasMoreTokens()) {
402             String aToken = tokens.nextToken();
403
404             if (aToken.length() < 3) {
405                 continue;
406             }
407
408             String attribute = aToken.substring(0, 3);
409
410             if ("CN=".equalsIgnoreCase(attribute)) {
411                 return aToken.substring(3);
412             }
413         }
414
415         return null;
416     }
417
418     /**
419      * Compute the signature of a stream.
420      *
421      * @param key    the private key used to sign the stream
422      * @param stream the stream to sign.
423      * @return byte[] the signature
424      */
425     public static byte[] computeSignature(String algorithm, PrivateKey key, InputStream stream) throws InvalidKeyException, SignatureException, IOException {
426         Signature sign;
427
428         try {
429             sign = Signature.getInstance(algorithm);
430         } catch (NoSuchAlgorithmException badsigner) {
431             throw new IOException("Could not initialize signer with algorithm " + algorithm);
432         }
433         sign.initSign(key, UTILS.srng);
434
435         byte[] buffer = new byte[1024];
436
437         while (true) {
438             int read = stream.read(buffer);
439
440             if (read < 0) {
441                 break;
442             }
443
444             sign.update(buffer, 0, read);
445         }
446
447         return sign.sign();
448     }
449
450     /**
451      * Verify a signature of a stream.
452      *
453      * @param cert      The certificate containing the public key which will be used
454      *                  to verify the signature.
455      * @param signature The signature to verify.
456      * @param stream    The stream to verify.
457      * @return boolean true if the signature was valid otherwise false.
458      */
459     public static boolean verifySignature(String algorithm, Certificate cert, byte[] signature, InputStream stream) throws InvalidKeyException, SignatureException, IOException {
460         Signature sign;
461
462         try {
463             sign = Signature.getInstance(algorithm);
464         } catch (NoSuchAlgorithmException badsigner) {
465             throw new IOException("Could not initialize signer with algorithm " + algorithm);
466         }
467
468         sign.initVerify(cert);
469
470         byte[] buffer = new byte[1024];
471
472         while (true) {
473             int read = stream.read(buffer);
474
475             if (read < 0) {
476                 break;
477             }
478
479             sign.update(buffer, 0, read);
480         }
481
482         return sign.verify(signature);
483     }
484
485     /**
486      * returns a hash SHA-1 of the given byte array
487      *
488      * @param data the data to be hashed
489      * @return byte[] the hash of the data
490      */
491     public static byte[] hash(String algorithm, byte[] data) {
492         try {
493             MessageDigest digest = MessageDigest.getInstance(algorithm);
494
495             return digest.digest(data);
496         } catch (NoSuchAlgorithmException e) {
497             return null;
498         }
499     }
500
501     /**
502      * We are trying to use : PBEWITHMD5ANDDES
503      */
504     static final String PKCS5_PBSE1_ALGO = "PBEWITHMD5ANDDES";
505
506     /**
507      * Given a private key and a password, encrypt the private key using the
508      * PBESE1 algorithm.
509      *
510      * @param password   The password which will be used.
511      * @param privkey    The private key to be encrypted.
512      * @param iterations Number of iterations.
513      * @return An encrypted private key info or null if the key could not be
514      *         encrypted.
515      */
516     public static EncryptedPrivateKeyInfo pkcs5_Encrypt_pbePrivateKey(char[] password, PrivateKey privkey, int iterations) {
517         if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
518             LOG.fine("Encrypting " + privkey + " with \'" + new String(password) + "\'");
519         }
520
521         PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
522         byte[] salt = new byte[8];
523
524         UTILS.srng.nextBytes(salt);
525
526         try {
527             PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, iterations);
528
529             // convert password into a SecretKey object, using a PBE key factory.
530             SecretKeyFactory keyFac = SecretKeyFactory.getInstance(PKCS5_PBSE1_ALGO);
531             SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
532
533             // Create PBE Cipher
534             Cipher pbeCipher = Cipher.getInstance(PKCS5_PBSE1_ALGO);
535
536             // Initialize PBE Cipher with key and parameters
537             pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
538
539             byte[] encryptedPrivKey = pbeCipher.doFinal(privkey.getEncoded());
540
541             AlgorithmParameters algo = AlgorithmParameters.getInstance(PKCS5_PBSE1_ALGO);
542
543             algo.init(pbeParamSpec);
544
545             EncryptedPrivateKeyInfo result = new EncryptedPrivateKeyInfo(algo, encryptedPrivKey);
546
547             return result;
548         } catch (Exception failed) {
549             if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
550                 LOG.log(Level.WARNING, "Encrypt failed", failed);
551             }
552             return null;
553         }
554     }
555
556     /**
557      * Given an encrypted private key and a password, decrypt the private key
558      * using the PBESE1 algorithm.
559      *
560      * @param password         The password which will be used.
561      * @param encryptedPrivKey The private key to be encrypted.
562      * @return The decrypted private key or null if the key could not be decrpyted.
563      */
564     public static PrivateKey pkcs5_Decrypt_pbePrivateKey(char[] password, String algorithm, EncryptedPrivateKeyInfo encryptedPrivKey) {
565         if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
566             LOG.fine("Decrypting " + encryptedPrivKey + "/" + algorithm + " with \'" + new String(password) + "\'");
567         }
568
569         PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
570
571         try {
572             AlgorithmParameters algo = encryptedPrivKey.getAlgParameters();
573
574             if (null == algo) {
575                 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
576                     LOG.warning("Could not get algo parameters from " + encryptedPrivKey);
577                 }
578
579                 throw new IllegalStateException("Could not get algo parameters from " + encryptedPrivKey);
580             }
581
582             PBEParameterSpec pbeParamSpec = algo.getParameterSpec(PBEParameterSpec.class);
583
584             // convert password into a SecretKey object, using a PBE key factory.
585             try {
586                 SecretKeyFactory keyFac = SecretKeyFactory.getInstance(PKCS5_PBSE1_ALGO);
587                 SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
588
589                 // Create PBE Cipher
590                 Cipher pbeCipher = Cipher.getInstance(PKCS5_PBSE1_ALGO);
591
592                 // Initialize PBE Cipher with key and parameters
593                 pbeCipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);
594
595                 KeySpec key_spec;
596
597                 key_spec = encryptedPrivKey.getKeySpec(pbeCipher);
598
599                 KeyFactory kf = KeyFactory.getInstance(algorithm);
600
601                 return kf.generatePrivate(key_spec);
602             } catch (InvalidKeySpecException failed) {
603                 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
604                     LOG.warning("Incorrect key for " + encryptedPrivKey + " : " + failed);
605                 }
606                 return null;
607             }
608         } catch (Exception failed) {
609             if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
610                 LOG.log(Level.WARNING, "Decrypt failed", failed);
611             }
612             return null;
613         }
614     }
615
616     // Load a wrapped object in base64 format:
617     // The following three methods were modified
618     // from similar pureTLS methods.
619     /**
620      * WrappedObject.java
621      * <p/>
622      * Copyright (C) 1999, Claymore Systems, Inc.
623      * All Rights Reserved.
624      * <p/>
625      * ekr@rtfm.com  Fri Jun  4 09:11:27 1999
626      * <p/>
627      * This package is a SSLv3/TLS implementation written by Eric Rescorla
628      * <ekr@rtfm.com> and licensed by Claymore Systems, Inc.
629      * <p/>
630      * Redistribution and use in source and binary forms, with or without
631      * modification, are permitted provided that the following conditions
632      * are met:
633      * 1. Redistributions of source code must retain the above copyright
634      * notice, this list of conditions and the following disclaimer.
635      * 2. Redistributions in binary form must reproduce the above copyright
636      * notice, this list of conditions and the following disclaimer in the
637      * documentation and/or other materials provided with the distribution.
638      * 3. All advertising materials mentioning features or use of this software
639      * must display the following acknowledgement:
640      * This product includes software developed by Claymore Systems, Inc.
641      * 4. Neither the name of Claymore Systems, Inc. nor the name of Eric
642      * Rescorla may be used to endorse or promote products derived from this
643      * software without specific prior written permission.
644      * <p/>
645      * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
646      * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
647      * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
648      * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
649      * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
650      * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
651      * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
652      * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
653      * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
654      * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
655      * SUCH DAMAGE.
656      */
657
658     public static String loadBase64Object(BufferedReader rdr, String type) throws IOException {
659         if (null != findObject(rdr, type)) {
660             return readBase64Object(rdr, type);
661         } else {
662             return null;
663         }
664     }
665
666     public static byte[] loadObject(BufferedReader rdr, String type) throws IOException {
667         if (null != findObject(rdr, type)) {
668             return readObject(rdr, type);
669         } else {
670             return null;
671         }
672     }
673
674     public static String findObject(BufferedReader br, String type) throws IOException {
675         String prefix = "-----BEGIN ";
676         String suffix = (type == null) ? "-----" : type + "-----";
677
678         while (true) {
679             br.mark(1024);
680
681             String line = br.readLine();
682
683             if (null == line) {
684                 return null;
685             }
686
687             if (!line.startsWith(prefix)) {
688                 continue;
689             }
690
691             if (!line.endsWith(suffix)) {
692                 continue;
693             }
694
695             br.reset();
696
697             return line.substring(prefix.length(), line.length() - 5);
698         }
699     }
700
701     /**
702      * We read a block of n-lines (\n terminated) and return a String of n-lines
703      * concatenated together. This keeps the format consistent with the pureTLS
704      * requirements.
705      */
706     public static String readBase64Object(BufferedReader br, String type) throws IOException {
707         String line = br.readLine();
708
709         String prefix = "-----BEGIN ";
710         String suffix = (type == null) ? "-----" : type + "-----";
711
712         if (!line.startsWith(prefix) || !line.endsWith(suffix)) {
713             throw new IOException("Not at begining of object");
714         }
715
716         StringBuilder block = new StringBuilder();
717
718         while (true) {
719             line = br.readLine();
720
721             if (null == line) {
722                 break;
723             }
724
725             if (line.startsWith("-----END ")) {
726                 break;
727             }
728
729             block.append(line);
730             block.append('\n');
731         }
732
733         return block.toString();
734     }
735
736     /**
737      * Read an object
738      */
739     public static byte[] readObject(BufferedReader br, String type) throws IOException {
740         String base64 = readBase64Object(br, type);
741
742         return base64Decode(new StringReader(base64));
743     }
744
745     /**
746      *
747      */
748
749     /**
750      * Write an object that is already base64 encoded.
751      */
752     public static void writeBase64Object(BufferedWriter bw, String type, String object) throws IOException {
753
754         bw.write("-----BEGIN ");
755         bw.write(type);
756         bw.write("-----");
757         bw.newLine();
758
759         bw.write(object);
760
761         char lastChar = object.charAt(object.length() - 1);
762
763         if (('\n' != lastChar) && ('\r' != lastChar)) {
764             bw.newLine();
765         }
766
767         bw.write("-----END ");
768         bw.write(type);
769         bw.write("-----");
770         bw.newLine();
771
772         bw.flush();
773     }
774
775     public static void writeObject(BufferedWriter out, String type, byte[] object) throws IOException {
776         String base64 = base64Encode(object);
777
778         writeBase64Object(out, type, base64);
779     }
780
781     /**
782      * Convert a byte array into a BASE64 encoded String.
783      *
784      * @param in The bytes to be converted
785      * @return the BASE64 encoded String.
786      */
787     public static String base64Encode(byte[] in) throws IOException {
788         return base64Encode(in, true);
789     }
790
791     /**
792      * Convert a byte array into a BASE64 encoded String.
793      *
794      * @param in the bytes to be converted
795      * @return the BASE64 encoded String.
796      */
797     public static String base64Encode(byte[] in, boolean wrap) throws IOException {
798         StringWriter base64 = new StringWriter();
799
800         BASE64OutputStream b64os;
801
802         if (wrap) {
803             b64os = new BASE64OutputStream(base64, 72);
804         } else {
805             b64os = new BASE64OutputStream(base64);
806         }
807         b64os.write(in);
808         b64os.close();
809
810         String encoded = base64.toString();
811
812         if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
813             LOG.finer("Encoded " + in.length + " bytes -> " + encoded.length() + " characters.");
814         }
815
816         return encoded;
817     }
818
819     /**
820      * Convert a BASE64 Encoded String into byte array.
821      *
822      * @param in BASE64 encoded String
823      * @return the decoded bytes.
824      */
825     public static byte[] base64Decode(Reader in) throws IOException {
826         BASE64InputStream b64is = new BASE64InputStream(in);
827         ByteArrayOutputStream bos = new ByteArrayOutputStream();
828
829         do {
830             int c = b64is.read();
831
832             if (c < 0) {
833                 break;
834             }
835
836             bos.write(c);
837         } while (true);
838
839         byte[] result = bos.toByteArray();
840
841         if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
842             LOG.finer("Decoded " + result.length + " bytes.");
843         }
844
845         return result;
846     }
847
848     /**
849      * Private replacement for toHexString since we need the leading 0 digits.
850      * Returns a String containing byte value encoded as 2 hex characters.
851      *
852      * @param theByte a byte containing the value to be encoded.
853      * @return String containing byte value encoded as 2 hex characters.
854      */
855     private static String toHexDigits(byte theByte) {
856         final char[] HEXDIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
857         StringBuilder result = new StringBuilder(2);
858
859         result.append(HEXDIGITS[(theByte >>> 4) & 15]);
860         result.append(HEXDIGITS[theByte & 15]);
861
862         return result.toString();
863     }
864
865     private static String toHexDigits(byte[] bytes) {
866         StringBuilder encoded = new StringBuilder(bytes.length * 2);
867
868         // build the string.
869         for (byte aByte : bytes) {
870             encoded.append(toHexDigits(aByte).toUpperCase());
871         }
872         return encoded.toString();
873     }
874 }