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