1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44: import ;
45: import ;
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54:
55: import ;
56: import ;
57: import ;
58: import ;
59: import ;
60: import ;
61: import ;
62: import ;
63: import ;
64: import ;
65: import ;
66: import ;
67: import ;
68: import ;
69: import ;
70: import ;
71: import ;
72: import ;
73: import ;
74:
75:
87: public class CSPRNG
88: extends BasePRNG
89: {
90: private static final Logger log = Logger.getLogger(CSPRNG.class.getName());
91:
105: public static final String FILE_SOURCES = "gnu.crypto.prng.pool.files";
106:
111: public static final String URL_SOURCES = "gnu.crypto.prng.pool.urls";
112:
118: public static final String PROGRAM_SOURCES = "gnu.crypto.prng.pool.programs";
119:
123: public static final String OTHER_SOURCES = "gnu.crypto.prng.pool.other";
124:
128: public static final String BLOCKING = "gnu.crypto.prng.pool.blocking";
129: private static final String FILES = "gnu.crypto.csprng.file.";
130: private static final String URLS = "gnu.crypto.csprng.url.";
131: private static final String PROGS = "gnu.crypto.csprng.program.";
132: private static final String OTHER = "gnu.crypto.csprng.other.";
133: private static final String BLOCK = "gnu.crypto.csprng.blocking";
134: private static final int POOL_SIZE = 256;
135: private static final int ALLOC_SIZE = 260;
136: private static final int OUTPUT_SIZE = POOL_SIZE / 2;
137: private static final int X917_POOL_SIZE = 16;
138: private static final String HASH_FUNCTION = Registry.SHA160_HASH;
139: private static final String CIPHER = Registry.AES_CIPHER;
140: private static final int MIX_COUNT = 10;
141: private static final int X917_LIFETIME = 8192;
142:
143: private static final int SPINNER_COUNT = 8;
144:
150: private static final Spinner[] SPINNERS = new Spinner[SPINNER_COUNT];
151: private static final Thread[] SPINNER_THREADS = new Thread[SPINNER_COUNT];
152: static
153: {
154: for (int i = 0; i < SPINNER_COUNT; i++)
155: {
156: SPINNER_THREADS[i] = new Thread(SPINNERS[i] = new Spinner(),
157: "spinner-" + i);
158: SPINNER_THREADS[i].setDaemon(true);
159: SPINNER_THREADS[i].setPriority(Thread.MIN_PRIORITY);
160: SPINNER_THREADS[i].start();
161: }
162: }
163:
164: private final IMessageDigest hash;
165:
166: private final IBlockCipher cipher;
167:
168: private int mixCount;
169:
170: private final byte[] pool;
171:
172: private double quality;
173:
174: private int index;
175:
176: private byte[] x917pool;
177:
178: private int x917count;
179:
180: private boolean x917init;
181:
182: private final List files;
183:
184: private final List urls;
185:
186: private final List progs;
187:
188: private final List other;
189:
190: private boolean blocking;
191:
192: private Poller poller;
193: private Thread pollerThread;
194:
195: public CSPRNG()
196: {
197: super("CSPRNG");
198: pool = new byte[ALLOC_SIZE];
199: x917pool = new byte[X917_POOL_SIZE];
200: x917count = 0;
201: x917init = false;
202: quality = 0.0;
203: hash = HashFactory.getInstance(HASH_FUNCTION);
204: cipher = CipherFactory.getInstance(CIPHER);
205: buffer = new byte[OUTPUT_SIZE];
206: ndx = 0;
207: initialised = false;
208: files = new LinkedList();
209: urls = new LinkedList();
210: progs = new LinkedList();
211: other = new LinkedList();
212: }
213:
214:
285: public static IRandom getSystemInstance() throws ClassNotFoundException,
286: MalformedURLException, NumberFormatException
287: {
288: CSPRNG instance = new CSPRNG();
289: HashMap attrib = new HashMap();
290: attrib.put(BLOCKING, Boolean.valueOf(getProperty(BLOCK)));
291: String s = null;
292:
293: List l = new LinkedList();
294: for (int i = 0; (s = getProperty(FILES + i)) != null; i++)
295: try
296: {
297: l.add(parseString(s.trim()));
298: }
299: catch (NumberFormatException nfe)
300: {
301: }
302: attrib.put(FILE_SOURCES, l);
303: l = new LinkedList();
304: for (int i = 0; (s = getProperty(URLS + i)) != null; i++)
305: try
306: {
307: l.add(parseURL(s.trim()));
308: }
309: catch (NumberFormatException nfe)
310: {
311: }
312: catch (MalformedURLException mue)
313: {
314: }
315: attrib.put(URL_SOURCES, l);
316: l = new LinkedList();
317: for (int i = 0; (s = getProperty(PROGS + i)) != null; i++)
318: try
319: {
320: l.add(parseString(s.trim()));
321: }
322: catch (NumberFormatException nfe)
323: {
324: }
325: attrib.put(PROGRAM_SOURCES, l);
326: l = new LinkedList();
327: for (int i = 0; (s = getProperty(OTHER + i)) != null; i++)
328: try
329: {
330: Class c = Class.forName(s.trim());
331: l.add(c.newInstance());
332: }
333: catch (ClassNotFoundException cnfe)
334: {
335: }
336: catch (InstantiationException ie)
337: {
338: }
339: catch (IllegalAccessException iae)
340: {
341: }
342: attrib.put(OTHER_SOURCES, l);
343: instance.init(attrib);
344: return instance;
345: }
346:
347: private static String getProperty(final String name)
348: {
349: return (String) AccessController.doPrivileged(new PrivilegedAction()
350: {
351: public Object run()
352: {
353: return Properties.getProperty(name);
354: }
355: });
356: }
357:
358: private static List parseString(String s) throws NumberFormatException
359: {
360: StringTokenizer tok = new StringTokenizer(s, ";");
361: if (tok.countTokens() != 4)
362: throw new IllegalArgumentException("malformed property");
363: Double quality = new Double(tok.nextToken());
364: Integer offset = new Integer(tok.nextToken());
365: Integer length = new Integer(tok.nextToken());
366: String str = tok.nextToken();
367: return new SimpleList(quality, offset, length, str);
368: }
369:
370: private static List parseURL(String s) throws MalformedURLException,
371: NumberFormatException
372: {
373: StringTokenizer tok = new StringTokenizer(s, ";");
374: if (tok.countTokens() != 4)
375: throw new IllegalArgumentException("malformed property");
376: Double quality = new Double(tok.nextToken());
377: Integer offset = new Integer(tok.nextToken());
378: Integer length = new Integer(tok.nextToken());
379: URL url = new URL(tok.nextToken());
380: return new SimpleList(quality, offset, length, url);
381: }
382:
383: public Object clone()
384: {
385: return new CSPRNG();
386: }
387:
388: public void setup(Map attrib)
389: {
390: List list = null;
391: if (Configuration.DEBUG)
392: log.fine("attrib=" + String.valueOf(attrib));
393: try
394: {
395: list = (List) attrib.get(FILE_SOURCES);
396: if (Configuration.DEBUG)
397: log.fine("list=" + String.valueOf(list));
398: if (list != null)
399: {
400: files.clear();
401: for (Iterator it = list.iterator(); it.hasNext();)
402: {
403: List l = (List) it.next();
404: if (Configuration.DEBUG)
405: log.fine("l=" + l);
406: if (l.size() != 4)
407: {
408: if (Configuration.DEBUG)
409: log.fine("file list too small: " + l.size());
410: throw new IllegalArgumentException("invalid file list");
411: }
412: Double quality = (Double) l.get(0);
413: Integer offset = (Integer) l.get(1);
414: Integer length = (Integer) l.get(2);
415: String source = (String) l.get(3);
416: files.add(new SimpleList(quality, offset, length, source));
417: }
418: }
419: }
420: catch (ClassCastException cce)
421: {
422: if (Configuration.DEBUG)
423: log.log(Level.FINE, "bad file list", cce);
424: throw new IllegalArgumentException("invalid file list");
425: }
426: try
427: {
428: list = (List) attrib.get(URL_SOURCES);
429: if (Configuration.DEBUG)
430: log.fine("list=" + String.valueOf(list));
431: if (list != null)
432: {
433: urls.clear();
434: for (Iterator it = list.iterator(); it.hasNext();)
435: {
436: List l = (List) it.next();
437: if (Configuration.DEBUG)
438: log.fine("l=" + l);
439: if (l.size() != 4)
440: {
441: if (Configuration.DEBUG)
442: log.fine("URL list too small: " + l.size());
443: throw new IllegalArgumentException("invalid URL list");
444: }
445: Double quality = (Double) l.get(0);
446: Integer offset = (Integer) l.get(1);
447: Integer length = (Integer) l.get(2);
448: URL source = (URL) l.get(3);
449: urls.add(new SimpleList(quality, offset, length, source));
450: }
451: }
452: }
453: catch (ClassCastException cce)
454: {
455: if (Configuration.DEBUG)
456: log.log(Level.FINE, "bad URL list", cce);
457: throw new IllegalArgumentException("invalid URL list");
458: }
459: try
460: {
461: list = (List) attrib.get(PROGRAM_SOURCES);
462: if (Configuration.DEBUG)
463: log.fine("list=" + String.valueOf(list));
464: if (list != null)
465: {
466: progs.clear();
467: for (Iterator it = list.iterator(); it.hasNext();)
468: {
469: List l = (List) it.next();
470: if (Configuration.DEBUG)
471: log.fine("l=" + l);
472: if (l.size() != 4)
473: {
474: if (Configuration.DEBUG)
475: log.fine("program list too small: " + l.size());
476: throw new IllegalArgumentException("invalid program list");
477: }
478: Double quality = (Double) l.get(0);
479: Integer offset = (Integer) l.get(1);
480: Integer length = (Integer) l.get(2);
481: String source = (String) l.get(3);
482: progs.add(new SimpleList(quality, offset, length, source));
483: }
484: }
485: }
486: catch (ClassCastException cce)
487: {
488: if (Configuration.DEBUG)
489: log.log(Level.FINE, "bad program list", cce);
490: throw new IllegalArgumentException("invalid program list");
491: }
492: try
493: {
494: list = (List) attrib.get(OTHER_SOURCES);
495: if (Configuration.DEBUG)
496: log.fine("list=" + String.valueOf(list));
497: if (list != null)
498: {
499: other.clear();
500: for (Iterator it = list.iterator(); it.hasNext();)
501: {
502: EntropySource src = (EntropySource) it.next();
503: if (Configuration.DEBUG)
504: log.fine("src=" + src);
505: if (src == null)
506: throw new NullPointerException("null source in source list");
507: other.add(src);
508: }
509: }
510: }
511: catch (ClassCastException cce)
512: {
513: throw new IllegalArgumentException("invalid source list");
514: }
515:
516: try
517: {
518: Boolean block = (Boolean) attrib.get(BLOCKING);
519: if (block != null)
520: blocking = block.booleanValue();
521: else
522: blocking = true;
523: }
524: catch (ClassCastException cce)
525: {
526: throw new IllegalArgumentException("invalid blocking parameter");
527: }
528: poller = new Poller(files, urls, progs, other, this);
529: try
530: {
531: fillBlock();
532: }
533: catch (LimitReachedException lre)
534: {
535: throw new RuntimeException("bootstrapping CSPRNG failed");
536: }
537: }
538:
539: public void fillBlock() throws LimitReachedException
540: {
541: if (Configuration.DEBUG)
542: log.fine("fillBlock");
543: if (getQuality() < 100.0)
544: {
545: if (Configuration.DEBUG)
546: log.fine("doing slow poll");
547: slowPoll();
548: }
549: do
550: {
551: fastPoll();
552: mixRandomPool();
553: }
554: while (mixCount < MIX_COUNT);
555: if (! x917init || x917count >= X917_LIFETIME)
556: {
557: mixRandomPool(pool);
558: Map attr = new HashMap();
559: byte[] key = new byte[32];
560: System.arraycopy(pool, 0, key, 0, 32);
561: cipher.reset();
562: attr.put(IBlockCipher.KEY_MATERIAL, key);
563: try
564: {
565: cipher.init(attr);
566: }
567: catch (InvalidKeyException ike)
568: {
569: throw new Error(ike.toString());
570: }
571: mixRandomPool(pool);
572: generateX917(pool);
573: mixRandomPool(pool);
574: generateX917(pool);
575: if (x917init)
576: quality = 0.0;
577: x917init = true;
578: x917count = 0;
579: }
580: byte[] export = new byte[ALLOC_SIZE];
581: for (int i = 0; i < ALLOC_SIZE; i++)
582: export[i] = (byte)(pool[i] ^ 0xFF);
583: mixRandomPool();
584: mixRandomPool(export);
585: generateX917(export);
586: for (int i = 0; i < OUTPUT_SIZE; i++)
587: buffer[i] = (byte)(export[i] ^ export[i + OUTPUT_SIZE]);
588: Arrays.fill(export, (byte) 0);
589: }
590:
591:
602: public synchronized void addRandomBytes(byte[] buf, int off, int len)
603: {
604: if (off < 0 || len < 0 || off + len > buf.length)
605: throw new ArrayIndexOutOfBoundsException();
606: if (Configuration.DEBUG)
607: {
608: log.fine("adding random bytes:");
609: log.fine(Util.toString(buf, off, len));
610: }
611: final int count = off + len;
612: for (int i = off; i < count; i++)
613: {
614: pool[index++] ^= buf[i];
615: if (index == pool.length)
616: {
617: mixRandomPool();
618: index = 0;
619: }
620: }
621: }
622:
623:
630: public synchronized void addRandomByte(byte b)
631: {
632: if (Configuration.DEBUG)
633: log.fine("adding byte " + Integer.toHexString(b));
634: pool[index++] ^= b;
635: if (index >= pool.length)
636: {
637: mixRandomPool();
638: index = 0;
639: }
640: }
641:
642: synchronized void addQuality(double quality)
643: {
644: if (Configuration.DEBUG)
645: log.fine("adding quality " + quality);
646: if (this.quality < 100)
647: this.quality += quality;
648: if (Configuration.DEBUG)
649: log.fine("quality now " + this.quality);
650: }
651:
652: synchronized double getQuality()
653: {
654: return quality;
655: }
656:
657:
662: private void mixRandomPool(byte[] buf)
663: {
664: int hashSize = hash.hashSize();
665: for (int i = 0; i < buf.length; i += hashSize)
666: {
667:
668: if (i == 0)
669: hash.update(buf, buf.length - hashSize, hashSize);
670: else
671: hash.update(buf, i - hashSize, hashSize);
672:
673: if (i + 64 < buf.length)
674: hash.update(buf, i, 64);
675: else
676: {
677: hash.update(buf, i, buf.length - i);
678: hash.update(buf, 0, 64 - (buf.length - i));
679: }
680: byte[] digest = hash.digest();
681: System.arraycopy(digest, 0, buf, i, hashSize);
682: }
683: }
684:
685: private void mixRandomPool()
686: {
687: mixRandomPool(pool);
688: mixCount++;
689: }
690:
691: private void generateX917(byte[] buf)
692: {
693: int off = 0;
694: for (int i = 0; i < buf.length; i += X917_POOL_SIZE)
695: {
696: int copy = Math.min(buf.length - i, X917_POOL_SIZE);
697: for (int j = 0; j < copy; j++)
698: x917pool[j] ^= pool[off + j];
699: cipher.encryptBlock(x917pool, 0, x917pool, 0);
700: System.arraycopy(x917pool, 0, buf, off, copy);
701: cipher.encryptBlock(x917pool, 0, x917pool, 0);
702: off += copy;
703: x917count++;
704: }
705: }
706:
707:
715: private void fastPoll()
716: {
717: byte b = 0;
718: for (int i = 0; i < SPINNER_COUNT; i++)
719: b ^= SPINNERS[i].counter;
720: addRandomByte(b);
721: addRandomByte((byte) System.currentTimeMillis());
722: addRandomByte((byte) Runtime.getRuntime().freeMemory());
723: String s = Thread.currentThread().getName();
724: if (s != null)
725: {
726: byte[] buf = s.getBytes();
727: addRandomBytes(buf, 0, buf.length);
728: }
729: ByteArrayOutputStream bout = new ByteArrayOutputStream(1024);
730: PrintStream pout = new PrintStream(bout);
731: Throwable t = new Throwable();
732: t.printStackTrace(pout);
733: pout.flush();
734: byte[] buf = bout.toByteArray();
735: addRandomBytes(buf, 0, buf.length);
736: }
737:
738: private void slowPoll() throws LimitReachedException
739: {
740: if (Configuration.DEBUG)
741: log.fine("poller is alive? "
742: + (pollerThread == null ? false : pollerThread.isAlive()));
743: if (pollerThread == null || ! pollerThread.isAlive())
744: {
745: boolean interrupted = false;
746: pollerThread = new Thread(poller);
747: pollerThread.setDaemon(true);
748: pollerThread.setPriority(Thread.NORM_PRIORITY - 1);
749: pollerThread.start();
750: if (blocking)
751: try
752: {
753: pollerThread.join();
754: }
755: catch (InterruptedException ie)
756: {
757: interrupted = true;
758: }
759:
760:
761: if (! interrupted && blocking && quality < 100.0)
762: {
763: if (Configuration.DEBUG)
764: log.fine("insufficient quality: " + quality);
765: throw new LimitReachedException("insufficient randomness was polled");
766: }
767: }
768: }
769:
770: protected void finalize() throws Throwable
771: {
772: if (poller != null && pollerThread != null && pollerThread.isAlive())
773: {
774: pollerThread.interrupt();
775: poller.stopUpdating();
776: pollerThread.interrupt();
777: }
778: Arrays.fill(pool, (byte) 0);
779: Arrays.fill(x917pool, (byte) 0);
780: Arrays.fill(buffer, (byte) 0);
781: }
782:
783:
789: private static class Spinner
790: implements Runnable
791: {
792: protected byte counter;
793:
794: private Spinner()
795: {
796: }
797:
798: public void run()
799: {
800: while (true)
801: {
802: counter++;
803: try
804: {
805: Thread.sleep(100);
806: }
807: catch (InterruptedException ie)
808: {
809: }
810: }
811: }
812: }
813:
814: private final class Poller
815: implements Runnable
816: {
817: private final List files;
818: private final List urls;
819: private final List progs;
820: private final List other;
821: private final CSPRNG pool;
822: private boolean running;
823:
824: Poller(List files, List urls, List progs, List other, CSPRNG pool)
825: {
826: super();
827: this.files = Collections.unmodifiableList(files);
828: this.urls = Collections.unmodifiableList(urls);
829: this.progs = Collections.unmodifiableList(progs);
830: this.other = Collections.unmodifiableList(other);
831: this.pool = pool;
832: }
833:
834: public void run()
835: {
836: running = true;
837: if (Configuration.DEBUG)
838: {
839: log.fine("files: " + files);
840: log.fine("URLs: " + urls);
841: log.fine("progs: " + progs);
842: }
843: Iterator files_it = files.iterator();
844: Iterator urls_it = urls.iterator();
845: Iterator prog_it = progs.iterator();
846: Iterator other_it = other.iterator();
847:
848: while (files_it.hasNext() || urls_it.hasNext() || prog_it.hasNext()
849: || other_it.hasNext())
850: {
851:
852: if (pool.getQuality() >= 100.0 || ! running)
853: return;
854: if (files_it.hasNext())
855: try
856: {
857: List l = (List) files_it.next();
858: if (Configuration.DEBUG)
859: log.fine(l.toString());
860: double qual = ((Double) l.get(0)).doubleValue();
861: int offset = ((Integer) l.get(1)).intValue();
862: int count = ((Integer) l.get(2)).intValue();
863: String src = (String) l.get(3);
864: InputStream in = new FileInputStream(src);
865: byte[] buf = new byte[count];
866: if (offset > 0)
867: in.skip(offset);
868: int len = in.read(buf);
869: if (len >= 0)
870: {
871: pool.addRandomBytes(buf, 0, len);
872: pool.addQuality(qual * ((double) len / (double) count));
873: }
874: if (Configuration.DEBUG)
875: log.fine("got " + len + " bytes from " + src);
876: }
877: catch (Exception x)
878: {
879: if (Configuration.DEBUG)
880: log.throwing(this.getClass().getName(), "run", x);
881: }
882: if (pool.getQuality() >= 100.0 || ! running)
883: return;
884: if (urls_it.hasNext())
885: try
886: {
887: List l = (List) urls_it.next();
888: if (Configuration.DEBUG)
889: log.fine(l.toString());
890: double qual = ((Double) l.get(0)).doubleValue();
891: int offset = ((Integer) l.get(1)).intValue();
892: int count = ((Integer) l.get(2)).intValue();
893: URL src = (URL) l.get(3);
894: InputStream in = src.openStream();
895: byte[] buf = new byte[count];
896: if (offset > 0)
897: in.skip(offset);
898: int len = in.read(buf);
899: if (len >= 0)
900: {
901: pool.addRandomBytes(buf, 0, len);
902: pool.addQuality(qual * ((double) len / (double) count));
903: }
904: if (Configuration.DEBUG)
905: log.fine("got " + len + " bytes from " + src);
906: }
907: catch (Exception x)
908: {
909: if (Configuration.DEBUG)
910: log.throwing(this.getClass().getName(), "run", x);
911: }
912: if (pool.getQuality() >= 100.0 || ! running)
913: return;
914: Process proc = null;
915: if (prog_it.hasNext())
916: try
917: {
918: List l = (List) prog_it.next();
919: if (Configuration.DEBUG)
920: log.finer(l.toString());
921: double qual = ((Double) l.get(0)).doubleValue();
922: int offset = ((Integer) l.get(1)).intValue();
923: int count = ((Integer) l.get(2)).intValue();
924: String src = (String) l.get(3);
925: proc = null;
926: proc = Runtime.getRuntime().exec(src);
927: InputStream in = proc.getInputStream();
928: byte[] buf = new byte[count];
929: if (offset > 0)
930: in.skip(offset);
931: int len = in.read(buf);
932: if (len >= 0)
933: {
934: pool.addRandomBytes(buf, 0, len);
935: pool.addQuality(qual * ((double) len / (double) count));
936: }
937: proc.destroy();
938: proc.waitFor();
939: if (Configuration.DEBUG)
940: log.fine("got " + len + " bytes from " + src);
941: }
942: catch (Exception x)
943: {
944: if (Configuration.DEBUG)
945: log.throwing(this.getClass().getName(), "run", x);
946: try
947: {
948: if (proc != null)
949: {
950: proc.destroy();
951: proc.waitFor();
952: }
953: }
954: catch (Exception ignored)
955: {
956: }
957: }
958: if (pool.getQuality() >= 100.0 || ! running)
959: return;
960: if (other_it.hasNext())
961: try
962: {
963: EntropySource src = (EntropySource) other_it.next();
964: byte[] buf = src.nextBytes();
965: if (pool == null)
966: return;
967: pool.addRandomBytes(buf, 0, buf.length);
968: pool.addQuality(src.quality());
969: if (Configuration.DEBUG)
970: log.fine("got " + buf.length + " bytes from " + src);
971: }
972: catch (Exception x)
973: {
974: if (Configuration.DEBUG)
975: log.throwing(this.getClass().getName(), "run", x);
976: }
977: }
978: }
979:
980: public void stopUpdating()
981: {
982: running = false;
983: }
984: }
985: }