Changed the way the FIPS RNG is seeded.
authorWerner Koch <wk@gnupg.org>
Fri, 29 Aug 2008 11:09:26 +0000 (11:09 +0000)
committerWerner Koch <wk@gnupg.org>
Fri, 29 Aug 2008 11:09:26 +0000 (11:09 +0000)
FIPS cleanups.
Documentation upodates.

22 files changed:
cipher/ChangeLog
cipher/cipher.c
cipher/elgamal.c
cipher/primegen.c
doc/ChangeLog
doc/Makefile.am
doc/gcrypt.texi
random/ChangeLog
random/random-csprng.c
random/random-daemon.c
random/random-fips.c
random/rndunix.c
random/rndw32.c
src/ChangeLog
src/global.c
src/hwfeatures.c
tests/ChangeLog
tests/Makefile.am
tests/README [new file with mode: 0644]
tests/basic.c
tests/pkbench.c
tests/rsa-16k.key [new file with mode: 0644]

index 60e7694..28fdf07 100644 (file)
@@ -1,3 +1,10 @@
+2008-08-28  Werner Koch  <wk@g10code.com>
+
+       * cipher.c (cipher_decrypt, cipher_encrypt): Return an error if
+       mode NONE is used.
+       (gcry_cipher_open): Allow mode NONE only with a debug flag set and
+       if not in FIPS mode.
+
 2008-08-26  Werner Koch  <wk@g10code.com>
 
        * pubkey.c (pubkey_generate): Add arg KEYGEN_FLAGS.
index e328435..782ff18 100644 (file)
@@ -731,7 +731,11 @@ gcry_cipher_open (gcry_cipher_hd_t *handle,
        break;
 
       case GCRY_CIPHER_MODE_NONE:
-       /* FIXME: issue a warning when this mode is used */
+        /* This mode may be used for debbuging.  It copies the main
+           text verbatim to the ciphertext.  We do not allow this in
+           fips mode or if no debug flag has been set.  */
+       if (fips_mode () || !_gcry_get_debug_flag (0))
+          err = GPG_ERR_INV_CIPHER_MODE;
        break;
 
       default:
@@ -1421,8 +1425,16 @@ cipher_encrypt (gcry_cipher_hd_t c, byte *outbuf,
                                outbuf, (byte*)/*arggg*/inbuf, nbytes );
         break;
       case GCRY_CIPHER_MODE_NONE:
-       if( inbuf != outbuf )
-           memmove( outbuf, inbuf, nbytes );
+               if (fips_mode () || !_gcry_get_debug_flag (0))
+          {
+            fips_signal_error ("cipher mode NONE used");
+            rc = GPG_ERR_INV_CIPHER_MODE;
+          }
+        else
+          {
+            if ( inbuf != outbuf )
+              memmove (outbuf, inbuf, nbytes);
+          }
        break;
       default:
         log_fatal("cipher_encrypt: invalid mode %d\n", c->mode );
@@ -1512,8 +1524,16 @@ cipher_decrypt (gcry_cipher_hd_t c, byte *outbuf, const byte *inbuf,
                                outbuf, (byte*)/*arggg*/inbuf, nbytes );
         break;
       case GCRY_CIPHER_MODE_NONE:
-       if( inbuf != outbuf )
-           memmove( outbuf, inbuf, nbytes );
+               if (fips_mode () || !_gcry_get_debug_flag (0))
+          {
+            fips_signal_error ("cipher mode NONE used");
+            rc = GPG_ERR_INV_CIPHER_MODE;
+          }
+        else
+          {
+            if (inbuf != outbuf)
+              memmove (outbuf, inbuf, nbytes);
+          }
        break;
       default:
         log_fatal ("cipher_decrypt: invalid mode %d\n", c->mode );
index 4a76e91..04ad6fa 100644 (file)
@@ -83,8 +83,9 @@ progress (int c)
 
 
 /****************
- * Michael Wiener's table on subgroup sizes to match field sizes
- * (floating around somewhere - Fixme: need a reference)
+ * Michael Wiener's table on subgroup sizes to match field sizes.
+ * (floating around somewhere, probably based on the paper from
+ * Eurocrypt 96, page 332)
  */
 static unsigned int
 wiener_map( unsigned int n )
index b9b31c6..9434868 100644 (file)
@@ -395,8 +395,7 @@ prime_generate_internal (int need_q_factor,
   /* Make a pool of 3n+5 primes (this is an arbitrary value).  We
      require at least 30 primes for are useful selection process. 
      
-     FIXME: We need to do some reseacrh on the best formula for sizing
-     the pool.
+     Fixme: We need to research the best formula for sizing the pool.
   */
   m = n * 3 + 5;
   if (need_q_factor) /* Need some more in this case. */
index 193ac2e..36170c9 100644 (file)
@@ -1,3 +1,7 @@
+2008-08-27  Werner Koch  <wk@g10code.com>
+
+       * Makefile.am (online): Take care of development versions.
+
 2008-08-18  Werner Koch  <wk@g10code.com>
 
        * gcrypt.texi (Top): Remove the detailmenu.
index 5d356a0..7862110 100644 (file)
@@ -45,11 +45,27 @@ gcrypt_TEXINFOS = lgpl.texi gpl.texi libgcrypt-modules.fig fips-fsm.fig
        fig2dev -L pdf `test -f '$<' || echo '$(srcdir)/'`$< $@
 
 
+# Make sure that gcrypt.texi is touched if any other source file has
+# been modified.  This is required so that the version.texi magic
+# updates the release date.
+gnupg.texi : $(gcrypt_TEXINFOS)
+       touch $(srcdir)/gcrypt.texi
+
 online: gcrypt.html gcrypt.pdf gcrypt.info
        set -e; \
        echo "Uploading current manuals to www.gnupg.org ..."; \
-        user=werner ; dir="webspace/manuals/gcrypt-devel/" ; \
-       (cd gcrypt.html && rsync -vr --exclude='.svn' .  \
-         $${user}@cvs.gnupg.org:$${dir} ); \
-        rsync -v gcrypt.pdf gcrypt.info $${user}@cvs.gnupg.org:$${dir}
+       cp libgcrypt-modules.png gcrypt.html/; \
+       cp fips-fsm.png gcrypt.html/; \
+        user=werner ; dashdevel="" ; \
+        if echo "@PACKAGE_VERSION@" | grep -- "-svn" >/dev/null; then \
+         dashdevel="-devel" ; \
+         cp gcrypt.pdf gcrypt.html/; \
+         cp gcrypt.info gcrypt.html/; \
+       else \
+          rsync -v gcrypt.pdf gcrypt.info \
+               $${user}@cvs.gnupg.org:webspace/manuals/ ; \
+        fi ; \
+       cd gcrypt.html ; \
+        rsync -vr --exclude='.svn' .  \
+         $${user}@cvs.gnupg.org:webspace/manuals/gcrypt$${dashdevel}/ 
 
index 11cb51e..b3daa07 100644 (file)
@@ -1540,8 +1540,9 @@ number.
 
 @table @code
 @item GCRY_CIPHER_MODE_NONE
-No mode specified, may be set later using other functions.  The value
-of this constant is always 0.
+No mode specified.  This should not be used.  The only exception is that
+if Libgcrypt is not used in FIPS mode and if any debug flag has been
+set, this mode may be used to bypass the actual encryption.
 
 @item GCRY_CIPHER_MODE_ECB
 Electronic Codebook mode.  
@@ -4621,14 +4622,95 @@ TBD.
 @node FIPS Restrictions
 @appendix Restrictions in FIPS mode
 
-If Libgcrypt is used FIPS mode these restrictions are effective:
+If Libgcrypt is used in FIPS mode these restrictions are effective:
 
 @itemize
+@item
+The cryptographic algorithms are restricted to this list:
+
+@table @asis
+@item GCRY_CIPHER_3DES
+3 key EDE Triple-DES symmetric encryption.
+@item GCRY_CIPHER_AES128
+AES 128 bit symmetric encryption.
+@item GCRY_CIPHER_AES192
+AES 192 bit symmetric encryption.
+@item GCRY_CIPHER_AES256
+AES 256 bit symmetric encryption.
+@item GCRY_MD_SHA1
+SHA-1 message digest.
+@item GCRY_MD_SHA224
+SHA-224 message digest.
+@item GCRY_MD_SHA256
+SHA-256 message digest.
+@item GCRY_MD_SHA384
+SHA-384 message digest.
+@item GCRY_MD_SHA512
+SHA-512 message digest.
+@item GCRY_MD_SHA1,GCRY_MD_FLAG_HMAC
+HMAC using a SHA-1 message digest.
+@item GCRY_MD_SHA224,GCRY_MD_FLAG_HMAC
+HMAC using a SHA-224 message digest.
+@item GCRY_MD_SHA256,GCRY_MD_FLAG_HMAC
+HMAC using a SHA-256 message digest.
+@item GCRY_MD_SHA384,GCRY_MD_FLAG_HMAC
+HMAC using a SHA-384 message digest.
+@item GCRY_MD_SHA512,GCRY_MD_FLAG_HMAC
+HMAC using a SHA-512 message digest.
+@item GCRY_PK_RSA
+RSA encryption and signing.         
+@item GCRY_PK_DSA
+DSA signing.
+@end table
+
+Note that the CRC algorithms are not considered cryptographic algorithms
+and thus are in addition available.
+
+@item
+RSA and DSA key generation refuses to create a key with a keysize of
+less than 1024 bits.  
+
+@item
+The @code{transient-key} flag for RSA key generation is ignored.
+
+@item
+Support for the VIA Padlock engine is disabled.
 
 @item 
-It may only be used on systesm with a /dev/random device.  Swicthing
-into FIPS mode on other systems will fail at runtime.
+FIPS mode may only be used on systems with a /dev/random device.
+Switching into FIPS mode on other systems will fail at runtime.
 
+@item
+Saving and loading a random seed file is not ignored.
+
+@item
+An X9.31 style random number generator is used in place of the
+large-pool-CSPRNG generator.
+
+@item
+The Alternative Public Key Interface (@code{gcry_ac_xxx}) is not
+supported and all API calls return an error.
+
+@item Registration of external modules is not supported.
+
+@item 
+Message digest debugging is disabled.
+
+@item
+All debug output related to cryptographic data is suppressed.
+
+@item 
+On-the-fly self-tests are not performed, instead of this self-tests are
+run before entering operational state.
+
+@item
+The function @code{gcry_set_allocation_handler} may not be used.  If it
+is used Libgcrypt will enter the error state.
+
+@item
+A handler set by @code{gcry_set_outofcore_handler} is ignored.
+@item
+A handler set by @code{gcry_set_fatalerror_handler} is ignored.
 
 
 @end itemize
@@ -4799,6 +4881,14 @@ self-tests to get to get back into operational state after an error.
 
 @bye
 
+GCRYCTL_SET_RANDOM_DAEMON_SOCKET
+GCRYCTL_USE_RANDOM_DAEMON
+The random damon is still a bit experimental, thus we do not document
+them.  Not ethat they should be used during initialization and that
+these functions are not really thread safe.
+
+
+
 
 @c  LocalWords:  int HD
 
index cd35870..df3cac7 100644 (file)
@@ -1,3 +1,22 @@
+2008-08-29  Werner Koch  <wk@g10code.com>
+
+       * random-fips.c (SEED_TTL): New.
+       (struct rng_context): Add USE_COUNTER, remove NEED_STRONG_ENTROPY.
+       (x931_aes_driver): Do re-seeding if required.
+       (x931_generate_key, x931_generate_seed): Factor common code out to ..
+       (get_entropy): .. new.  Always use /dev/random.
+       (x931_generate_key): Seed key for nonce_context from std_rng_context.
+       (x931_reseed): New. Seed nonce context from std_rng_context.
+       (get_random): Use x931_reseed.
+       (_gcry_rngfips_selftest): Return an error if no /dev/radom support
+       has been compiled in.
+       (get_random): Remove locking.
+       (_gcry_rngfips_randomize, _gcry_rngfips_create_nonce): Lock here.
+
+2008-08-28  Werner Koch  <wk@g10code.com>
+
+       * random-daemon.c (connect_to_socket): Use GPG_ERR_ENAMETOOLONG.
+
 2008-08-25  Werner Koch  <wk@g10code.com>
 
        * random-fips.c (x931_aes): Take datetime_GT from an arg.
index eda34f7..39c49a7 100644 (file)
@@ -367,9 +367,9 @@ _gcry_rngcsprng_initialize (int full)
 void
 _gcry_rngcsprng_dump_stats (void)
 {
-  /* FIXME: don't we need proper locking here? -mo.  
-     Yes. However this is usually called during cleanup and thenwe _
-     might_ run into problems.  Needs to be checked.  -wk */
+  /* In theory we would need to lock the stats here.  However this
+     function is usually called during cleanup and then we _might_ run
+     into problems.  */
 
   log_info ("random usage: poolsize=%d mixed=%lu polls=%lu/%lu added=%lu/%lu\n"
            "              outmix=%lu getlvl1=%lu/%lu getlvl2=%lu/%lu%s\n",
@@ -422,7 +422,11 @@ _gcry_rngcsprng_use_daemon (int onoff)
 #ifdef USE_RANDOM_DAEMON
   int last;
   
-  /* FIXME: This is not really thread safe. */
+  /* This is not really thread safe.  However it is expected that this
+     function is being called during initialization and at that point
+     we are for other reasons not really thread safe.  We do not want
+     to lock it because we might eventually decide that this function
+     may even be called prior to gcry_check_version.  */
   last = allow_daemon;
   if (onoff != -1)
     allow_daemon = onoff;
index 0b79e5c..2e03ba0 100644 (file)
@@ -90,7 +90,7 @@ connect_to_socket (const char *socketname, int *sock)
   if (strlen (socketname) + 1 >= sizeof (srvr_addr->sun_path))
     {
       log_error ("socket name `%s' too long\n", socketname);
-      err = gcry_error (GPG_ERR_INTERNAL); /* FIXME? */
+      err = gcry_error (GPG_ERR_ENAMETOOLONG);
       goto out;
     }
   strcpy (srvr_addr->sun_path, socketname);
@@ -285,7 +285,7 @@ call_daemon (const char *socketname,
          break;
        }
 
-      /*      if (1)*/                 /* FIXME, verbose */
+      /*      if (1)*/                 /* Do this in verbose mode? */
       /*       log_info ("received response with %d bytes of data\n", buf[1]);*/
 
       if (buf[1] < nbytes)
index 57a6573..68f0ec4 100644 (file)
    The core of this deterministic random number generator is
    implemented according to the document "NIST-Recommended Random
    Number Generator Based on ANSI X9.31 Appendix A.2.4 Using the 3-Key
-   Triple DES and AES Algorithms" (2005-01-31).  This implementaion
+   Triple DES and AES Algorithms" (2005-01-31).  This implementation
    uses the AES variant.
+
+   There are 3 random context which map to the different levels of
+   random quality:
+
+   Generator                Seed and Key        Kernel entropy (init/reseed)
+   ------------------------------------------------------------
+   GCRY_VERY_STRONG_RANDOM  /dev/random         256/128 bits
+   GCRY_STRONG_RANDOM       /dev/random         256/128 bits
+   gcry_create_nonce        GCRY_STRONG_RANDOM  n/a
+
+   All random generators return their data in 128 bit blocks.  If the
+   caller requested less bits, the extra bits are not used.  The key
+   for each generator is only set once at the first time a generator
+   is used.  The seed value is set with the key and again after 1000
+   (SEED_TTL) output blocks.
+
+   The GCRY_VERY_STRONG_RANDOM and GCRY_STRONG_RANDOM generators are
+   keyed and seeded from the /dev/random device.  Thus these
+   generators may block until the kernel has collected enough entropy.
+
+   The gcry_create_nonce generator is keyed and seeded from the
+   GCRY_STRONG_RANDOM generator.  It may also block if the
+   GCRY_STRONG_RANDOM generator has not yet been used before and thus
+   gets initialized on the first use by gcry_create_nonce.  This
+   special treatment is justified by the weaker requirements for a
+   nonce generator and to save precious kernel entropy for use by the
+   real random generators.
+
  */
 
 #include <config.h>
@@ -59,6 +87,11 @@ static int fips_rng_is_locked;
 static unsigned char *tempvalue_for_x931_aes_driver;
 
 
+/* After having retrieved this number of blocks from the RNG, we want
+   to do a reseeding.  */
+#define SEED_TTL 1000
+
+
 /* The length of the key we use:  16 bytes (128 bit) for AES128.  */
 #define X931_AES_KEYLEN  16
 /* A global buffer used to communicate between the x931_generate_key
@@ -83,10 +116,6 @@ struct rng_context
      established.  */
   gcry_cipher_hd_t cipher_hd;
 
-  /* If this flag is true, this context requires strong entropy;
-     i.e. from /dev/random.  */
-  int need_strong_entropy:1;
-
   /* If this flag is true, the SEED_V buffer below carries a valid
      seed.  */
   int is_seeded:1;
@@ -96,6 +125,9 @@ struct rng_context
      is available.  */
   int compare_value_valid:1;
 
+  /* A counter used to trigger re-seeding.  */
+  unsigned int use_counter;
+
   unsigned char guard_1[1];
 
   /* The buffer containing the seed value V.  */
@@ -140,6 +172,11 @@ static rng_context_t std_rng_context;
 static rng_context_t strong_rng_context;
 
 
+/* --- Local prototypes ---  */
+static void x931_reseed (rng_context_t rng_ctx);
+static void get_random (void *buffer, size_t length, rng_context_t rng_ctx);
+
+
 
 \f
 /* --- Functions  --- */
@@ -412,6 +449,13 @@ x931_aes_driver (unsigned char *output, size_t length, rng_context_t rng_ctx)
 
   while (length)
     {
+      /* We require a new seed after some time.  */
+      if (rng_ctx->use_counter > SEED_TTL)
+        {
+          x931_reseed (rng_ctx);
+          rng_ctx->use_counter = 0;
+        }
+
       /* Due to the design of the RNG, we always receive 16 bytes (128
          bit) of random even if we require less.  The extra bytes
          returned are not used.  Intheory we could save them for the
@@ -423,6 +467,7 @@ x931_aes_driver (unsigned char *output, size_t length, rng_context_t rng_ctx)
       x931_aes (result_buffer,
                 datetime_DT, rng_ctx->seed_V, rng_ctx->cipher_hd,
                 intermediate_I, temp_buffer);
+      rng_ctx->use_counter++;
 
       /* Do a basic check on the output to avoid a stuck generator.  */
       if (!rng_ctx->compare_value_valid)
@@ -455,9 +500,9 @@ x931_aes_driver (unsigned char *output, size_t length, rng_context_t rng_ctx)
 
 
 /* Callback for x931_generate_key. Note that this callback uses the
-   global ENTROPY_COLLECT_BUFFER which has been setup by
-   x931_generate_key.  ORIGIN is not used but required due to the
-   emtropy gathering module. */
+   global ENTROPY_COLLECT_BUFFER which has been setup by get_entropy.
+   ORIGIN is not used but required due to the design of entropy
+   gathering module. */
 static void
 entropy_collect_cb (const void *buffer, size_t length,
                     enum random_origins origin)
@@ -476,15 +521,49 @@ entropy_collect_cb (const void *buffer, size_t length,
     }
 }
 
+
+/* Get NBYTES of entropy from the kernel device.  The callers needs to
+   free the returned buffer.  The function either succeeds or
+   terminates the process in case of a fatal error. */
+static void *
+get_entropy (size_t nbytes)
+{
+#if USE_RNDLINUX
+  void *result;
+
+  gcry_assert (!entropy_collect_buffer);
+  entropy_collect_buffer = gcry_xmalloc_secure (nbytes);
+  entropy_collect_buffer_size = nbytes;
+  entropy_collect_buffer_len = 0;
+  if (_gcry_rndlinux_gather_random (entropy_collect_cb, 0,
+                                    X931_AES_KEYLEN,
+                                    GCRY_VERY_STRONG_RANDOM) < 0
+      || entropy_collect_buffer_len != entropy_collect_buffer_size)
+    {
+      gcry_free (entropy_collect_buffer);
+      entropy_collect_buffer = NULL;
+      log_fatal ("error getting entropy data\n");
+    }
+  result = entropy_collect_buffer;
+  entropy_collect_buffer = NULL;
+  return result;
+#else
+  log_fatal ("/dev/random support is not compiled in\n");
+  return NULL;  /* NOTREACHED */
+#endif
+}
+
+
 /* Generate a key for use with x931_aes.  The function returns a
    handle to the cipher context readily prepared for ECB encryption.
-   If VERY_STRONG is true the key is read from /dev/random, otherwise
-   from /dev/urandom.  On error NULL is returned.  */
+   If FOR_NONCE is true, the key is retrieved by readong random from
+   the standard generator.  On error NULL is returned.  */
 static gcry_cipher_hd_t
-x931_generate_key (int very_strong)
+x931_generate_key (int for_nonce)
 {
   gcry_cipher_hd_t hd;
   gpg_error_t err;
+  void *buffer;
 
   gcry_assert (fips_rng_is_locked);
 
@@ -498,34 +577,22 @@ x931_generate_key (int very_strong)
       return NULL;
     }
 
-  /* Get a key from the entropy source.  */
-#if USE_RNDLINUX
-  gcry_assert (!entropy_collect_buffer);
-  entropy_collect_buffer = gcry_xmalloc_secure (X931_AES_KEYLEN);
-  entropy_collect_buffer_size = X931_AES_KEYLEN;
-  entropy_collect_buffer_len = 0;
-  if (_gcry_rndlinux_gather_random (entropy_collect_cb, 0, X931_AES_KEYLEN,
-                                    (very_strong
-                                     ? GCRY_VERY_STRONG_RANDOM
-                                     : GCRY_STRONG_RANDOM)
-                                    ) < 0
-      || entropy_collect_buffer_len != entropy_collect_buffer_size)
+  /* Get a key from the standard RNG or from the entropy source.  */
+  if (for_nonce)
     {
-      gcry_free (entropy_collect_buffer);
-      entropy_collect_buffer = NULL;
-      gcry_cipher_close (hd);
-      log_fatal ("error getting entropy data for the RNG key\n");
+      buffer = gcry_xmalloc (X931_AES_KEYLEN);
+      get_random (buffer, X931_AES_KEYLEN, std_rng_context);
+    }
+  else
+    {
+      buffer = get_entropy (X931_AES_KEYLEN);
     }
-#else
-  log_fatal ("/dev/random support is not compiled in\n");
-#endif
 
   /* Set the key and delete the buffer because the key is now part of
      the cipher context.  */
-  err = gcry_cipher_setkey (hd, entropy_collect_buffer, X931_AES_KEYLEN);
-  wipememory (entropy_collect_buffer, X931_AES_KEYLEN);
-  gcry_free (entropy_collect_buffer);
-  entropy_collect_buffer = NULL;
+  err = gcry_cipher_setkey (hd, buffer, X931_AES_KEYLEN);
+  wipememory (buffer, X931_AES_KEYLEN);
+  gcry_free (buffer);
   if (err)
     {
       log_error ("error creating key for RNG: %s\n", gcry_strerror (err));
@@ -540,35 +607,43 @@ x931_generate_key (int very_strong)
 /* Generate a key for use with x931_aes.  The function copies a seed
    of LENGTH bytes into SEED_BUFFER. LENGTH needs to by given as 16.  */
 static void
-x931_generate_seed (unsigned char *seed_buffer, size_t length, int very_strong)
+x931_generate_seed (unsigned char *seed_buffer, size_t length)
 {
+  void *buffer;
+
   gcry_assert (fips_rng_is_locked);
   gcry_assert (length == 16);
 
-  /* Get a seed from the entropy source.  */
-#if USE_RNDLINUX
-  gcry_assert (!entropy_collect_buffer);
-  entropy_collect_buffer = gcry_xmalloc_secure (X931_AES_KEYLEN);
-  entropy_collect_buffer_size = X931_AES_KEYLEN;
-  entropy_collect_buffer_len = 0;
-  if (_gcry_rndlinux_gather_random (entropy_collect_cb, 0, X931_AES_KEYLEN,
-                                    (very_strong
-                                     ? GCRY_VERY_STRONG_RANDOM
-                                     : GCRY_STRONG_RANDOM)
-                                    ) < 0
-      || entropy_collect_buffer_len != entropy_collect_buffer_size)
+  buffer = get_entropy (X931_AES_KEYLEN);
+
+  memcpy (seed_buffer, buffer, X931_AES_KEYLEN);
+  wipememory (buffer, X931_AES_KEYLEN);
+  gcry_free (buffer);
+}
+
+
+
+/* Reseed a generator.  This is also used for the initial seeding. */
+static void
+x931_reseed (rng_context_t rng_ctx)
+{
+  gcry_assert (fips_rng_is_locked);
+
+  if (rng_ctx == nonce_context)
     {
-      gcry_free (entropy_collect_buffer);
-      entropy_collect_buffer = NULL;
-      log_fatal ("error getting entropy data for the RNG seed\n");
+      /* The nonce context is special.  It will be seeded using the
+         standard random generator.  */
+      get_random (rng_ctx->seed_V, 16, std_rng_context);
+      rng_ctx->is_seeded = 1;
+      rng_ctx->seed_init_pid = getpid ();
+    }
+  else
+    {
+      /* The other two generators are seeded from /dev/random.  */
+      x931_generate_seed (rng_ctx->seed_V, 16);
+      rng_ctx->is_seeded = 1;
+      rng_ctx->seed_init_pid = getpid ();
     }
-#else
-  log_fatal ("/dev/random support is not compiled in\n");
-#endif
-  memcpy (seed_buffer, entropy_collect_buffer, X931_AES_KEYLEN);
-  wipememory (entropy_collect_buffer, X931_AES_KEYLEN);
-  gcry_free (entropy_collect_buffer);
-  entropy_collect_buffer = NULL;
 }
 
 
@@ -582,13 +657,15 @@ get_random (void *buffer, size_t length, rng_context_t rng_ctx)
   gcry_assert (buffer);
   gcry_assert (rng_ctx);
 
-  lock_rng ();
   check_guards (rng_ctx);
 
   /* Initialize the cipher handle and thus setup the key if needed.  */
   if (!rng_ctx->cipher_hd)
     {
-      rng_ctx->cipher_hd = x931_generate_key (rng_ctx->need_strong_entropy);
+      if (rng_ctx == nonce_context)
+        rng_ctx->cipher_hd = x931_generate_key (1);
+      else
+        rng_ctx->cipher_hd = x931_generate_key (0);
       if (!rng_ctx->cipher_hd)
         goto bailout;
       rng_ctx->key_init_pid = getpid ();
@@ -596,11 +673,7 @@ get_random (void *buffer, size_t length, rng_context_t rng_ctx)
 
   /* Initialize the seed value if needed.  */
   if (!rng_ctx->is_seeded)
-    {
-      x931_generate_seed (rng_ctx->seed_V, 16, rng_ctx->need_strong_entropy);
-      rng_ctx->is_seeded = 1;
-      rng_ctx->seed_init_pid = getpid ();
-    }
+    x931_reseed (rng_ctx);
 
   if (rng_ctx->key_init_pid != getpid ()
       || rng_ctx->seed_init_pid != getpid ())
@@ -618,17 +691,17 @@ get_random (void *buffer, size_t length, rng_context_t rng_ctx)
     goto bailout;
 
   check_guards (rng_ctx);
-  unlock_rng ();
   return;
 
  bailout:
-  unlock_rng ();
   log_fatal ("severe error getting random\n");
   /*NOTREACHED*/
 }
 
 
 \f
+/* --- Public Functions --- */
+
 /* Initialize this random subsystem.  If FULL is false, this function
    merely calls the basic initialization of the module and does not do
    anything more.  Doing this is not really required but when running
@@ -658,7 +731,6 @@ _gcry_rngfips_initialize (int full)
       setup_guards (std_rng_context);
       
       strong_rng_context = gcry_xcalloc_secure (1, sizeof *strong_rng_context);
-      strong_rng_context->need_strong_entropy = 1;
       setup_guards (strong_rng_context);
     }
   else
@@ -713,10 +785,12 @@ _gcry_rngfips_randomize (void *buffer, size_t length,
 {
   _gcry_rngfips_initialize (1);  /* Auto-initialize if needed.  */
   
+  lock_rng ();
   if (level == GCRY_VERY_STRONG_RANDOM)
     get_random (buffer, length, strong_rng_context);
   else
     get_random (buffer, length, std_rng_context);
+  unlock_rng ();
 }
 
 
@@ -726,7 +800,9 @@ _gcry_rngfips_create_nonce (void *buffer, size_t length)
 {
   _gcry_rngfips_initialize (1);  /* Auto-initialize if needed.  */
 
+  lock_rng ();
   get_random (buffer, length, nonce_context);
+  unlock_rng ();
 }
 
 
@@ -876,16 +952,24 @@ gcry_error_t
 _gcry_rngfips_selftest (selftest_report_func_t report)
 {
   gcry_err_code_t ec;
-  char buffer[8];
 
-  /* Do a simple test using the public interface.  This will also
-     enforce full intialization of the RNG.  We need to be fully
-     initialized due to the global requirement of the
-     tempvalue_for_x931_aes_driver stuff. */
-  gcry_randomize (buffer, sizeof buffer, GCRY_STRONG_RANDOM);
+#if USE_RNDLINUX
+  {
+    char buffer[8];
+
+    /* Do a simple test using the public interface.  This will also
+       enforce full intialization of the RNG.  We need to be fully
+       initialized due to the global requirement of the
+       tempvalue_for_x931_aes_driver stuff. */
+    gcry_randomize (buffer, sizeof buffer, GCRY_STRONG_RANDOM);
+  }
 
   ec = selftest_kat (report);
 
+#else /*!USE_RNDLINUX*/
+  report ("random", 0, "setup", "no support for /dev/random");
+  ec = GPG_ERR_SELFTEST_FAILED;
+#endif
   return gpg_error (ec);
 }
 
index 0a04eb2..1faf9ab 100644 (file)
@@ -81,9 +81,6 @@
  =========
 */
 
-/* Fixme: We use plain mallocs here because it may be used as a module.
- * Should be changed. */
-
 /* General includes */
 
 #include <config.h>
@@ -717,7 +714,7 @@ start_gatherer( int pipefd )
     }
 
 
-    /* Set up the buffer */
+    /* Set up the buffer.  Not ethat we use a plain standard malloc here. */
     gather_buffer_size = GATHER_BUFSIZE;
     gather_buffer = malloc( gather_buffer_size );
     if( !gather_buffer ) {
index 1952979..c514019 100644 (file)
@@ -939,7 +939,7 @@ _gcry_rndw32_gather_random_fast (void (*add)(const void*, size_t,
      directly by checking for CPUID capabilities, and fall back to QPC if
      this isn't present.  */
 #ifdef __GNUC__  
-/*   FIXME: We need to implement the CPU feature tests first.  */
+/*   FIXME: We would need to implement the CPU feature tests first.  */
 /*   if (cpu_has_feature_rdtsc) */
 /*     { */
 /*       uint32_t lo, hi; */
index 578f5c8..d72ef54 100644 (file)
@@ -1,9 +1,19 @@
+2008-08-28  Werner Koch  <wk@g10code.com>
+
+       * hwfeatures.c (_gcry_detect_hw_features): Disable hardware
+       detection in FIPS mode.
+
 2008-08-27  Werner Koch  <wk@g10code.com>
 
        * global.c (_gcry_vcontrol): Allow running selftests from error
        state.
+       (gcry_set_outofcore_handler): Only print a warning if used in FIPS
+       mode.
+       (gcry_xmalloc, gcry_xrealloc, gcry_xmalloc_secure, gcry_xstrdup):
+       Ignore an outofcore handler in FIPS mode.
+
        * fips.c (_gcry_fips_test_error_or_operational): New.
-       (fips_new_state): Allow transtion from error into selftest.
+       (fips_new_state): Allow transition from error into selftest.
        Disallow error to init.
 
 2008-08-26  Werner Koch  <wk@g10code.com>
index 1d5314b..3b32ec6 100644 (file)
@@ -115,12 +115,11 @@ global_init (void)
   return;
 
  fail:
-  /* FIXME: use `err'?  */
   BUG ();
 }
 
-\f
 
+\f
 /* Version number parsing.  */
 
 /* This function parses the first portion of the version number S and
@@ -599,7 +598,7 @@ gcry_set_outofcore_handler( int (*f)( void*, size_t, unsigned int ),
 
   if (fips_mode () )
     {
-      fips_signal_error ("out of core handler used");
+      log_info ("out of core handler ignored in FIPS mode\n");
       return;
     }
   
@@ -780,13 +779,16 @@ gcry_strdup (const char *string)
 void *
 gcry_xmalloc( size_t n )
 {
-    void *p;
-
-    while ( !(p = gcry_malloc( n )) ) {
-       if( !outofcore_handler
-           || !outofcore_handler( outofcore_handler_value, n, 0 ) ) {
-           _gcry_fatal_error(gpg_err_code_from_errno (errno), NULL );
-       }
+  void *p;
+  
+  while ( !(p = gcry_malloc( n )) ) 
+    {
+      if ( fips_mode () 
+           || !outofcore_handler
+           || !outofcore_handler (outofcore_handler_value, n, 0) )
+        {
+          _gcry_fatal_error (gpg_err_code_from_errno (errno), NULL);
+        }
     }
     return p;
 }
@@ -794,13 +796,16 @@ gcry_xmalloc( size_t n )
 void *
 gcry_xrealloc( void *a, size_t n )
 {
-    void *p;
-
-    while ( !(p = gcry_realloc( a, n )) ) {
-       if( !outofcore_handler
-           || !outofcore_handler( outofcore_handler_value, n,
-                                   gcry_is_secure(a)? 3:2 ) ) {
-           _gcry_fatal_error(gpg_err_code_from_errno (errno), NULL );
+  void *p;
+  
+  while ( !(p = gcry_realloc( a, n )) )
+    {
+      if ( fips_mode ()
+           || !outofcore_handler
+           || !outofcore_handler (outofcore_handler_value, n,
+                                   gcry_is_secure(a)? 3:2 ) )
+        {
+          _gcry_fatal_error (gpg_err_code_from_errno (errno), NULL );
        }
     }
     return p;
@@ -809,16 +814,19 @@ gcry_xrealloc( void *a, size_t n )
 void *
 gcry_xmalloc_secure( size_t n )
 {
-    void *p;
-
-    while ( !(p = gcry_malloc_secure( n )) ) {
-       if( !outofcore_handler
-           || !outofcore_handler( outofcore_handler_value, n, 1 ) ) {
-           _gcry_fatal_error(gpg_err_code_from_errno (errno),
-                            _("out of core in secure memory"));
+  void *p;
+  
+  while ( !(p = gcry_malloc_secure( n )) ) 
+    {
+      if ( fips_mode ()
+           || !outofcore_handler
+           || !outofcore_handler (outofcore_handler_value, n, 1) )
+        {
+          _gcry_fatal_error (gpg_err_code_from_errno (errno),
+                             _("out of core in secure memory"));
        }
     }
-    return p;
+  return p;
 }
 
 
@@ -862,13 +870,14 @@ char *
 gcry_xstrdup (const char *string)
 {
   char *p;
-
+  
   while ( !(p = gcry_strdup (string)) ) 
     {
       size_t n = strlen (string);
       int is_sec = !!gcry_is_secure (string);
-
-      if (!outofcore_handler
+      
+      if (fips_mode ()
+          || !outofcore_handler
           || !outofcore_handler (outofcore_handler_value, n, is_sec) ) 
         {
           _gcry_fatal_error (gpg_err_code_from_errno (errno),
index 4dd4e4f..2621305 100644 (file)
@@ -145,7 +145,7 @@ detect_ia32_gnuc (void)
 
 
 
-/* Detect the available hardware features.  This fucntion is called
+/* Detect the available hardware features.  This function is called
    once right at startup and we assume that no other threads are
    running.  */
 void
@@ -153,6 +153,9 @@ _gcry_detect_hw_features (void)
 {
   hw_features = 0;
 
+  if (fips_mode ())
+    return; /* Hardware support is not to be evaluated.  */ 
+
 #if defined (__i386__) && SIZEOF_UNSIGNED_LONG == 4
 #ifdef __GNUC__  
   detect_ia32_gnuc ();
index aea5ce3..c08d544 100644 (file)
@@ -1,5 +1,15 @@
+2008-08-28  Werner Koch  <wk@g10code.com>
+
+       * rsa-16k.key: New sample key.
+
 2008-08-27  Werner Koch  <wk@g10code.com>
 
+       * pkbench.c (read_file): New.
+       (process_key_pair_file): Replace mmap by read_file.
+       (main): Add a --fips option.
+       * Makefile.am (EXTRA_DIST): Remove.
+       (EXTRA_PROGRAMS): Add pkbench.
+
        * basic.c (main): Extended FIPS self-test test.
 
 2008-08-26  Werner Koch  <wk@g10code.com>
index 4f6ab42..6c39d21 100644 (file)
@@ -38,9 +38,8 @@ AM_CFLAGS = $(GPG_ERROR_CFLAGS)
 
 LDADD = ../src/libgcrypt.la $(DL_LIBS)
 
-EXTRA_PROGRAMS = testapi
+EXTRA_PROGRAMS = testapi pkbench
 noinst_PROGRAMS = $(TESTS)
 
-# pkbench uses mmap for no good reason.  Needs to be fixed.  Code for
-# this can be found in libksba/tests. 
-EXTRA_DIST = pkbench.c
+EXTRA_DIST = README rsa-16k.key
+
diff --git a/tests/README b/tests/README
new file mode 100644 (file)
index 0000000..5326890
--- /dev/null
@@ -0,0 +1,9 @@
+Some notes about the tests.
+
+rsa-16k.key - A 16384 bit RSA key (public and privat), created 2008-08-28.
+              It took 91 minutes to create it on a 1500Mhz Pentium M.
+              pkpench showed these results:
+                encrypt:    80 ms
+                decrypt: 14370 ms
+                   sign: 14110 ms
+                 verify:    30 ms
index 70a76fe..aafd41c 100644 (file)
@@ -1,5 +1,5 @@
 /* basic.c  -  basic regression tests
- *     Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc.
+ * Copyright (C) 2001, 2002, 2003, 2005, 2008 Free Software Foundation, Inc.
  *
  * This file is part of Libgcrypt.
  *
@@ -14,8 +14,7 @@
  * GNU Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
index 1dbc28a..67b94dc 100644 (file)
@@ -1,5 +1,5 @@
 /* pkbench.c - Pubkey menchmarking
- *     Copyright (C) 2004, 2005 Free Software Foundation, Inc.
+ * Copyright (C) 2004, 2005, 2008 Free Software Foundation, Inc.
  *
  * This file is part of Libgcrypt.
  *
@@ -14,8 +14,7 @@
  * GNU Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
 #include <assert.h>
 #include <stdlib.h>
 #include <ctype.h>
-#include <sys/mman.h>
 #include <sys/stat.h>
 #ifndef HAVE_W32_SYSTEM
-#include <sys/times.h>
+# include <sys/times.h>
 #endif /*HAVE_W32_SYSTEM*/
 #include <unistd.h>
 #include <fcntl.h>
 #include <time.h>
+#include <errno.h>
 
 #define PGM "pkbench"
 
@@ -88,12 +87,50 @@ show_sexp (const char *prefix, gcry_sexp_t a)
 
   fputs (prefix, stderr);
   size = gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, NULL, 0);
-  buf = malloc (size);
-  if (!buf)
-    die ("out of core\n");
+  buf = gcry_xmalloc (size);
 
   gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, buf, size);
   fprintf (stderr, "%.*s", (int)size, buf);
+  gcry_free (buf);
+}
+
+
+static void *
+read_file (const char *fname, size_t *r_length)
+{
+  FILE *fp;
+  struct stat st;
+  char *buf;
+  size_t buflen;
+  
+  fp = fopen (fname, "rb");
+  if (!fp)
+    {
+      fail ("can't open `%s': %s\n", fname, strerror (errno));
+      return NULL;
+    }
+  
+  if (fstat (fileno(fp), &st))
+    {
+      fail ("can't stat `%s': %s\n", fname, strerror (errno));
+      fclose (fp);
+      return NULL;
+    }
+
+  buflen = st.st_size;
+  buf = gcry_xmalloc (buflen+1);
+  if (fread (buf, buflen, 1, fp) != 1)
+    {
+      fail ("error reading `%s': %s\n", fname, strerror (errno));
+      fclose (fp);
+      gcry_free (buf);
+      return NULL;
+    }
+  fclose (fp);
+
+  if (r_length)
+    *r_length = buflen;
+  return buf;
 }
 
 
@@ -312,23 +349,16 @@ process_key_pair_file (const char *key_pair_file)
   gcry_sexp_t key_secret_sexp = NULL;
   gcry_sexp_t key_public_sexp = NULL;
   struct context context = { NULL };
-  struct stat statbuf;
-  int key_pair_fd = -1;
-  int ret = 0;
-
-  ret = stat (key_pair_file, &statbuf);
-  assert (! ret);
-
-  key_pair_fd = open (key_pair_file, O_RDONLY);
-  assert (key_pair_fd != -1);
+  size_t file_length;
 
-  key_pair_buffer = mmap (NULL, statbuf.st_size, PROT_READ,
-                         MAP_PRIVATE, key_pair_fd, 0);
-  assert (key_pair_buffer != MAP_FAILED);
+  key_pair_buffer = read_file (key_pair_file, &file_length);
+  if (!key_pair_buffer)
+    die ("failed to open `%s'\n", key_pair_file);
 
   err = gcry_sexp_sscan (&key_pair_sexp, NULL,
-                        key_pair_buffer, statbuf.st_size);
-  assert (! err);
+                        key_pair_buffer, file_length);
+  if (err)
+    die ("gcry_sexp_sscan failed\n");
 
   key_secret_sexp = gcry_sexp_find_token (key_pair_sexp, "private-key", 0);
   assert (key_secret_sexp);
@@ -336,10 +366,6 @@ process_key_pair_file (const char *key_pair_file)
   assert (key_public_sexp);
 
   gcry_sexp_release (key_pair_sexp);
-  ret = munmap (key_pair_buffer, statbuf.st_size);
-  assert (! ret);
-  ret = close (key_pair_fd);
-  assert (! ret);
 
   context_init (&context, key_secret_sexp, key_public_sexp);
 
@@ -348,6 +374,7 @@ process_key_pair_file (const char *key_pair_file)
   printf ("\n");
 
   context_destroy (&context);
+  gcry_free (key_pair_buffer);
 }
 
 
@@ -380,13 +407,13 @@ generate_key (const char *algorithm, const char *key_size)
 
   key_pair_buffer_size = gcry_sexp_sprint (key_pair, GCRYSEXP_FMT_ADVANCED,
                                           NULL, 0);
-  key_pair_buffer = malloc (key_pair_buffer_size);
-  assert (key_pair_buffer);
+  key_pair_buffer = gcry_xmalloc (key_pair_buffer_size);
 
   gcry_sexp_sprint (key_pair, GCRYSEXP_FMT_ADVANCED,
                    key_pair_buffer, key_pair_buffer_size);
 
   printf ("%.*s", (int)key_pair_buffer_size, key_pair_buffer);
+  gcry_free (key_pair_buffer);
 }
 
 
@@ -396,17 +423,11 @@ main (int argc, char **argv)
 {
   int last_argc = -1;
   int genkey_mode = 0;
+  int fips_mode = 0;
 
   if (argc)
     { argc--; argv++; }
 
-  gcry_control (GCRYCTL_DISABLE_SECMEM);
-  if (!gcry_check_version (GCRYPT_VERSION))
-    {
-      fprintf (stderr, PGM ": version mismatch\n");
-      exit (1);
-    }
-
   while (argc && last_argc != argc )
     {
       last_argc = argc;
@@ -429,7 +450,7 @@ main (int argc, char **argv)
         }
       else if (!strcmp (*argv, "--verbose"))
         {
-          verbose = 1;
+          verbose++;
           argc--; argv++;
         }
       else if (!strcmp (*argv, "--debug"))
@@ -442,8 +463,25 @@ main (int argc, char **argv)
           genkey_mode = 1;
           argc--; argv++;
         }
+      else if (!strcmp (*argv, "--fips"))
+        {
+          fips_mode = 1;
+          argc--; argv++;
+        }
     }          
 
+  gcry_control (GCRYCTL_SET_VERBOSITY, (int)verbose);
+
+  if (fips_mode)
+    gcry_control (GCRYCTL_FORCE_FIPS_MODE, 0);
+
+  gcry_control (GCRYCTL_DISABLE_SECMEM);
+  if (!gcry_check_version (GCRYPT_VERSION))
+    {
+      fprintf (stderr, PGM ": version mismatch\n");
+      exit (1);
+    }
+
   if (genkey_mode)
     {
       /* No valuable keys are create, so we can speed up our RNG. */
diff --git a/tests/rsa-16k.key b/tests/rsa-16k.key
new file mode 100644 (file)
index 0000000..017915a
--- /dev/null
@@ -0,0 +1,18 @@
+(key-data 
+ (public-key 
+  (rsa 
+   (n #00D6007A7AD47BB8D6B356E4F24DFAEE3A722FEE77F7E9547F866CB369C233E6CB3916D416973E3157B4DC1837E6D4C907D1063855735EAA857176A7DA3CA9F378FF7AE9EF227C193965F106F35DB2A833D2760CF9F2D041938CD310D9CE38EDD179C33EBC4963A02221D8000FDBF4BEB592CAB1ED1EEEC9D6916F27263C76DE70184F5399DE3B3862227346B1B3FBA306174D08BEC3675E2593CFD42159655B0BE1A2B69C2BA9F4F03B8C6BA505F6BFDFC6163D74F42A6D4908284D6879CECCF6512F9225612E3030ACF3663DFB77B41AFFCFC70BC11B224E14B397D25AC15E4E342B34B363056EA76CB0265DBD41F733C7FDE98B7C2340289E338CD31F993ABFACA6E83B54BCB50DD1DD11165C188C80EBDA190A11B6D8982CDA1B6B9D1631AA3EACC93040237831A52D15826A0D3E92C833D0234975C92A7236F902FA6703C89C7779765020C11F714B4C9D33B76CD466DC2BE9A102488B0635F31E6FBB9282E5139D32623E10ED9C295DA3B39F68227218EDF8C6FE9372F174AE1DE5BBE7B0AF09A869CAEDBEFE05458BFD43CF32F10F5C345A2E3D588C8C16B4DA8B44FA9539C679B81133A35498696F5D866E3B6A89811AEA7BFD1BF690EC329D87989CDADA7EAB106785D2D6661BD400D76C113E28F13FD883027E1CAC848B13750C7CCD530273C165BDFDA93E6F72897E97F003308704B95801F223EE89160786B1DE440BA9C1F371CBA37E5B09650CDB3AA1ABAA237AD15B89DCD03390A28308643E219490BEC83403F6A09B94F81D7BB391C121FC9028A6908E5B287AC79209B905B33724B1869A679CB347BF192D80D2D66CF1DAEFEBBF22CEDB8CEC010D6F8D86CD055ED71425DA72DF1C07A573E6F070235C378DAB5404ED004B4946CCDA4786ACBBF379A47CC36A049C50651CA4B1CEF03EE87DB6D2484C3D10AF71798A6AD1E20780814F79348D45BD1004880D2DEDEBD152694C80B9F93DF32F5930911DB379B4CBB9230CFC5FC126B9B77F074B9C82BDB4F12471B3FE92079525FD276293B63B978B55E039024EE688180D7C7C6C094B754AB9B652AC31812F2F7E45EF2B6D4478D7C6E5C8F3CB0A4D04A3E693D1DD4D8F894E910D9A999DDABE0427A1AB0C715C5A695A69140B20B9DA1195E6C9536B5DF24B4D45ED24D0F2C276E3CF48066EFB977C2B7096B02EB52309D916BD432347D72799BF9D76A03D54DE211460017C0E268BC9E23B415ABF46EB8B939B5A413EBD3F20E95F704E1F2CDCDF974A8743923DBC6D8363DC8948BE85EF1D368CD3EABDBE5B82648D2F676EB310D7B77465D3A14B86050463E43AC745F3781E7A6F582BD7B8AB22BC4EEECD2CB155E6E0B2604843E3906D47EBDA2C10B6D8BFCBB5722CE5394EB50721E90EFD28C63A62269C8C14593D69076D0F198D2BDCCB6D753CB81C4BED56A90E2DDBFFC0B9076C65F973B5EA3242E71E3CBBFE0976CFE22475F56726058D2D0CE3BD52AA940A0F559DD055BE9A6F50846902E02B70DB4FF5BED33762E10409D25ABDACF661BD9BA2A22212E02893A1625CA44850887B4B3A00D0AF63645E2EC42333035062090E8E7E63037C692FBA0B3FC7F3686FC2831F4DE2D4D82CF6FD6321D6621C8227715E3772EE8805911AA9E67083C511F17863C4D6F2C29E19CF329200024E539A7C5BF1A9D601AFF8DB7CFD75C6532488469E44BAC7266A3C127720E640328F9970B75509E292CCEC0B55A1F729456CB2804BE50451185F8CDA313C7D4DF6C1C67D6C411025A2BFFF06C5062470F97B17E75B4F81CD1FEC777465D684849809B4281B690D2A8FE5C4FA87DB00328630FC31BFDDA4641B29CB434147806A614E450E3E2B50317E3B4EE6262A2D4D0A8FE7530CEDFBCB5016C4D6E61C34E61AFA324871A9C75F9BC6BF6C92B95910C9D0FE049AEEF2E96E4C9E69E1FCE1F6CC687D533668F55367E2695197BE392A7FE66C4F88C0B1A9DEC6DFF682675855979DEA2A5644748DD882CE1F0D8FDA8530617BAA130AD9C16ABF8D76B5853104AD2E0C54C9639C3F6E1343AC94139621245EE8E12CA4366A6EC752BD9D1A0948CCC3626CEDB882BA4638115BBF55444DD4544EEC561F0E762C9989A9306D4749ABD47C31F40AD3F735FEEE6E1FDCEB626073CD5F76730B348103B041B9EEB941EFA61581DD9278802A2934C33FF0668C25CEF2546C44263A68919ECBB540B4A18E1867EA15C9F7A2853F55EFBB01C3D27D28579E030D0A771B754680FCD46B56EBD3431C24F202A343E20294076E56A09FA5F6C3E844DAF5BDCFBFF55CCC3FDDDB060FBC680BA520153098E57FC7741D77DFA8932F9028D8E0E66600974A41DAC5BBA4690407AC36EC206655ADCECC8AA0471601F67C3DF48B830585FA15C52061C4FF958453B1E75626120CDC0ADCE44743027FA4C59C1931E90726CD2BE240D0DC6D61CDE5165350D86FFF17260A823C0AE3467A597D774A67BE843951975E17BC1CB69DC8A0C7BFF799FB8FD2BDB37853D2EB28C9B7B8A2212FC73FDF2F21FF3FBCD798533FC4867739E48BA061B174BAC224064F3E867A1CF52E091FDD36871955FBEA90CD3D23B1BF0039930E0636080E6A36206ED5DD1CE4546EC0B0802BBEE2869DCCAEA01B8FC3A6392820180AA4D99AB67C57E8FD0874E7C54BBC7B9A2AA4D1EA4ADC1A2802DF908AF74F915AF98EEEBF822AC958CD0D9AF5A754AB2F4790225F18864A94734E526BDE497FF21F3392472D4F0E3B7E2EE97DDCA15060BF35A05E2593418809D3C9738C328EB4D44F35E6C913069096B0742809F55F01D06D40EB0476C34950FDAEF9BD2CC1F7653B4BCF1AA304963530C8F0C39697EAD32ADF464E3CAC931D33992B357A3A231FB978A56C3592A61411A5428C3549A991D811#)
+   (e #010001#)
+   )
+  )
+ (private-key 
+  (rsa 
+   (n #00D6007A7AD47BB8D6B356E4F24DFAEE3A722FEE77F7E9547F866CB369C233E6CB3916D416973E3157B4DC1837E6D4C907D1063855735EAA857176A7DA3CA9F378FF7AE9EF227C193965F106F35DB2A833D2760CF9F2D041938CD310D9CE38EDD179C33EBC4963A02221D8000FDBF4BEB592CAB1ED1EEEC9D6916F27263C76DE70184F5399DE3B3862227346B1B3FBA306174D08BEC3675E2593CFD42159655B0BE1A2B69C2BA9F4F03B8C6BA505F6BFDFC6163D74F42A6D4908284D6879CECCF6512F9225612E3030ACF3663DFB77B41AFFCFC70BC11B224E14B397D25AC15E4E342B34B363056EA76CB0265DBD41F733C7FDE98B7C2340289E338CD31F993ABFACA6E83B54BCB50DD1DD11165C188C80EBDA190A11B6D8982CDA1B6B9D1631AA3EACC93040237831A52D15826A0D3E92C833D0234975C92A7236F902FA6703C89C7779765020C11F714B4C9D33B76CD466DC2BE9A102488B0635F31E6FBB9282E5139D32623E10ED9C295DA3B39F68227218EDF8C6FE9372F174AE1DE5BBE7B0AF09A869CAEDBEFE05458BFD43CF32F10F5C345A2E3D588C8C16B4DA8B44FA9539C679B81133A35498696F5D866E3B6A89811AEA7BFD1BF690EC329D87989CDADA7EAB106785D2D6661BD400D76C113E28F13FD883027E1CAC848B13750C7CCD530273C165BDFDA93E6F72897E97F003308704B95801F223EE89160786B1DE440BA9C1F371CBA37E5B09650CDB3AA1ABAA237AD15B89DCD03390A28308643E219490BEC83403F6A09B94F81D7BB391C121FC9028A6908E5B287AC79209B905B33724B1869A679CB347BF192D80D2D66CF1DAEFEBBF22CEDB8CEC010D6F8D86CD055ED71425DA72DF1C07A573E6F070235C378DAB5404ED004B4946CCDA4786ACBBF379A47CC36A049C50651CA4B1CEF03EE87DB6D2484C3D10AF71798A6AD1E20780814F79348D45BD1004880D2DEDEBD152694C80B9F93DF32F5930911DB379B4CBB9230CFC5FC126B9B77F074B9C82BDB4F12471B3FE92079525FD276293B63B978B55E039024EE688180D7C7C6C094B754AB9B652AC31812F2F7E45EF2B6D4478D7C6E5C8F3CB0A4D04A3E693D1DD4D8F894E910D9A999DDABE0427A1AB0C715C5A695A69140B20B9DA1195E6C9536B5DF24B4D45ED24D0F2C276E3CF48066EFB977C2B7096B02EB52309D916BD432347D72799BF9D76A03D54DE211460017C0E268BC9E23B415ABF46EB8B939B5A413EBD3F20E95F704E1F2CDCDF974A8743923DBC6D8363DC8948BE85EF1D368CD3EABDBE5B82648D2F676EB310D7B77465D3A14B86050463E43AC745F3781E7A6F582BD7B8AB22BC4EEECD2CB155E6E0B2604843E3906D47EBDA2C10B6D8BFCBB5722CE5394EB50721E90EFD28C63A62269C8C14593D69076D0F198D2BDCCB6D753CB81C4BED56A90E2DDBFFC0B9076C65F973B5EA3242E71E3CBBFE0976CFE22475F56726058D2D0CE3BD52AA940A0F559DD055BE9A6F50846902E02B70DB4FF5BED33762E10409D25ABDACF661BD9BA2A22212E02893A1625CA44850887B4B3A00D0AF63645E2EC42333035062090E8E7E63037C692FBA0B3FC7F3686FC2831F4DE2D4D82CF6FD6321D6621C8227715E3772EE8805911AA9E67083C511F17863C4D6F2C29E19CF329200024E539A7C5BF1A9D601AFF8DB7CFD75C6532488469E44BAC7266A3C127720E640328F9970B75509E292CCEC0B55A1F729456CB2804BE50451185F8CDA313C7D4DF6C1C67D6C411025A2BFFF06C5062470F97B17E75B4F81CD1FEC777465D684849809B4281B690D2A8FE5C4FA87DB00328630FC31BFDDA4641B29CB434147806A614E450E3E2B50317E3B4EE6262A2D4D0A8FE7530CEDFBCB5016C4D6E61C34E61AFA324871A9C75F9BC6BF6C92B95910C9D0FE049AEEF2E96E4C9E69E1FCE1F6CC687D533668F55367E2695197BE392A7FE66C4F88C0B1A9DEC6DFF682675855979DEA2A5644748DD882CE1F0D8FDA8530617BAA130AD9C16ABF8D76B5853104AD2E0C54C9639C3F6E1343AC94139621245EE8E12CA4366A6EC752BD9D1A0948CCC3626CEDB882BA4638115BBF55444DD4544EEC561F0E762C9989A9306D4749ABD47C31F40AD3F735FEEE6E1FDCEB626073CD5F76730B348103B041B9EEB941EFA61581DD9278802A2934C33FF0668C25CEF2546C44263A68919ECBB540B4A18E1867EA15C9F7A2853F55EFBB01C3D27D28579E030D0A771B754680FCD46B56EBD3431C24F202A343E20294076E56A09FA5F6C3E844DAF5BDCFBFF55CCC3FDDDB060FBC680BA520153098E57FC7741D77DFA8932F9028D8E0E66600974A41DAC5BBA4690407AC36EC206655ADCECC8AA0471601F67C3DF48B830585FA15C52061C4FF958453B1E75626120CDC0ADCE44743027FA4C59C1931E90726CD2BE240D0DC6D61CDE5165350D86FFF17260A823C0AE3467A597D774A67BE843951975E17BC1CB69DC8A0C7BFF799FB8FD2BDB37853D2EB28C9B7B8A2212FC73FDF2F21FF3FBCD798533FC4867739E48BA061B174BAC224064F3E867A1CF52E091FDD36871955FBEA90CD3D23B1BF0039930E0636080E6A36206ED5DD1CE4546EC0B0802BBEE2869DCCAEA01B8FC3A6392820180AA4D99AB67C57E8FD0874E7C54BBC7B9A2AA4D1EA4ADC1A2802DF908AF74F915AF98EEEBF822AC958CD0D9AF5A754AB2F4790225F18864A94734E526BDE497FF21F3392472D4F0E3B7E2EE97DDCA15060BF35A05E2593418809D3C9738C328EB4D44F35E6C913069096B0742809F55F01D06D40EB0476C34950FDAEF9BD2CC1F7653B4BCF1AA304963530C8F0C39697EAD32ADF464E3CAC931D33992B357A3A231FB978A56C3592A61411A5428C3549A991D811#)   
+   (e #010001#)
+   (d #0125A7ED14E014111AE2BD8FD81A69B0BDED886DBE477D9CC08C6B07F1F82BD5BD73797FF9FFFB0D2542BE97FD1DCE9FE30F516F117DB449B513C85EF779DC91DD57B6B2E1BDD077A1EBE148486C2ADC8FEAC7FE1BD40BDD45E6833B26FB75388D05293177EE12678B197B42EFD59A38985B4BB471A3761E41F1BA8AB3A2AFA5A241B999096B8A9809BC7C5DBB3BDF476049AE7671A47213C9922B7E4C1A5545BA92C555100DB00AB77254C8E1DFC283F3EDE901819B611CD5E551133D14E8FD18840F6331D29E2EED47118E7094C1E36E53DA2AA90133856A351367224B51F80C184A5C3C4CDA5CC822126B3DF696AE96BFB8B836FA56E4E8D7D8A545E5F668F23203AC6968BE0A8A0C3BEEAC0AC9CFAE994B8EA5E293A5B9817D49B89761528595BB99D83C2B1AA4054FA2FF1D1D4F8ADBC3E863D8F4BD8C76C38E057D81740FB4FB12BC3CF80AB510223934FE8D3FD461D17E9B4EA07380A7E5202DD93A40B1F2E6C2048160949A247AC9A3F962A4E2F4EE809F00C76AF8DA4737D1398E6A95BE4637C949A33492C9691B254EB239EA7B1EC0E2D4261A27183F90577F04B356FD10FDB5E23A4471068952930EAFF4EDC757ACE25781DBA807A0C153FEDDCCB55A08B774AB44AD2CC75BD319D4822BFC6AF24C9F837C72D1A615109882906ADC2B2C679630A6FAE363144B77A134F2856DF1D8E9A77AAEC08A72FD67C122BD280D591A6C4045D0497362FB91C8C38C00A457A0BBE8D625210E4BF55FC4041FBE0A1515B70EA98C4F4284B3C96C15DD21C8CC15305DB5BFA2C21EB9520C9ACD823F5E7ABCDA3993D89A7B561C101FEC08A8AE6621245CAA1406D7536FBC6E835692D2B1BB540B8F2B2EAB7A1406B2FE83873CDEDFB0A0E717A037CD3A6322AE0B6F5E36187646866A0D406F7F54007FEC9711311BCE87FC6B4F44C5D1F7BEDD2DED081F1439F38312FA27CB65665B1595F88713378797AB624C728CA6632653EC8E762A76E3E597AAD4C3C3FE41648AEB07FCB8BFE9C70A1818E4F1B19124BECD320E4CDA6A9FD02B0A422114B5DE31376549C3B5FE1A896F8FCB256B814BD100FFBF5359510D8FD243DB014DBCFA3036C857A41DCBAE29ECE25012AF0B88827A1B3F3FA6E75EDF1790B0CCD794D0E733315743EF50FA18E5E1A93DC5D1EB28F555C0A8541B729954EA1865C6FDBE810D153CB50C024E8E7A59D324C22B626A30F4AAA0FA46EDCA4CC42F4B2D033B147DA54A67D103633A88EBFFF0EFFB61AA98DD2B057700FAC0986A9FB27C2CC29EE30B699DE063C76A1E2863D13951C35C5ABD357555781D2A5E68B0BFACF2E11747006E0810DD0CF97D585318A3B0DF7A67465EC3761004AA9B7C141B4E5444D9EB649BBD94F983FCFAEC982D994C7A620DE2D8AEE012BFD22AA9322F3DD3BD5CE90C17660D18F8CB679B02BDABC0D4D0F876B0DD6D258E08DC50B35544A4BDBE5F75604493D5FDF98B7FB812475C7B7122DDEC512322E1855C31105397AF284B7C2B4DC315D2E8D5017BBD2400885D5EB6C321B5093EF98A14EB4C29DC2B7DF9565D9E23A2BE6A2E334CE3485A8677E463DD86F49E3B56D2974F7D930FFDD60BA54AB49AE9DCDC588A4FC7AFCDB89A7713C51FA97BFA8868EE207C3C0032FDB302E45DFA0DFD8A2DE4D50A959A424626CC92CE74F8A2627C3B20406D714BB64825FBAE1B44A2F7569E32A8CBB41DBA3D9115691B07C3951A2D1394BF06BB0690B710D438CCC4E5B92B0CF302109A60C836E3AB40E4D3579C2E1C77F432C62925A751D92A564440A563B3C373D9A46FFCB0EB478962450C11192FDE187718F9AEDFF61DFE39AE98714B1DD49C356593F23F2BADE0E753B25BDCB3D618E3CDC5C5D8F37974A449E6E2D21B46FA90435F399AFF98988DEA9048B8031E1AE2E4B2B7C3FBB7CA775B6058500DB8852FF7358251CBF12A3CA233719760D251939A29778D7B4BDE15B5244B8AB47555C18925222331443DD6B227282A101443F14851734796FF4E323B66ED8304E594973EDBCA57B9413BB83574673623CB9BA282F92FBE49BFC7D6D18644C1741DAFEE86B58A174AA1B576F2599CAA8F15C9AC513CCC3778EAB81D6EF90557E73E207E96BC2D83BD3FED3717B0E8E527AE27BD05B28962280099AD1AF8C8D0B0E668EDC73AB3BD7E21C9C9134BFBC837BBC8E68FE8DF48365491371D378CB768A24A956F0D625D47EDBBAB051E4B4EE5D59574A4F2C4371D491ED182CD945DDAF11EB17382A58F2AFB6B79DE3EA1636C67449340F77A1CF14DFEDCB42B18BFFB866DD007B9A606C7BAEA70F9C5EAC98CBB52ECE5932F2925ACEAAE412E6114090CB54145A751E430EF1CA3A418C4D76EA7D797FF882401FD21984F4FBC347C4BFBB5B2B946B65F4FE0C7B9E5E16CBC1B6612325A0C83A8740D5D885C443EB8D02289BFC72E3BD2925E599B863F117C8A32E81F4B6F2C0C5D8CEA6E75C03E564E3F8D1663B5F3D21D844B6F80FB982484809303019670115FF5CC5FF6681E1C9B9AB01EA719AECAB7A4C4F1044017449741C726A4D1D97D0FB0D390F37DE0F838038270AEDD5E058113252D4F8E91B1377FD24F528EEDF58AE575327BE17F4A9D68E24B39DDF1187C8ABCEB1A84AB6BAB0735F3756E512641E62D3B51DF0316D065949379DD06C07606A82126A129A2A70F91EF54096CFCBC3447B49D5DD5F7DC7AA9F3E86E8ECD581F51731DF726194CD3143CC1E608AE882EA8848CDFD9F3FF6282AC6D722C2D5F51F2652724FEB601E02E1F078E32B36892ED9E2DE0A637836A005B01CEFDADF90534049565E8B965224B3E7EFF5707F3DEB6BFDB8D8045549168CD1C81910E584978A555DE877CFBDCB8D7388D3081F9DD8067198562521FEE99C57E3401#)
+   (p #00E6BBCBEBFCA813CED7907F5FA73C4C2D3532AF7A7650C4A88563DF805D54D0CEA528347A60F703C8FFE91997505F8C238383E03C53DFD347D0E385A5CFFE4E2A944DD45ACBC481D54D7B22F4C59A2BFC8C686E527B907AD9C5ECE870D102550D8D4A02B404DAE7023CC8F0436DE5F50A7CE1F4E741147F676B5DC9437CB47727B93155AE5C1EBE236E9F436D723FF770104305A1460C21BE9361582CB107CAF036F0600BCC6D78DE73C1F44C25F377B9A65349E9B73C446E5C6281DCB68EC65AF684AB39F6C84CF96E4CD909B61FA2D8BBB69A7994B78865FFEACD3838F851C944039C2422B75AE4F9C2A1702B005641EB41CE9FB042757E86F6E9651428EBA4D908D60A99AD61E5D09E7A05C7E59A9615DE965200CFB75A228B1D5DB8D2C040A65D940D5516C9FC5069B94D2F1903EF6B07F70ECF3E8D720F74139A4647D79131835CF7F15EA839350A00BD9D733359503497BCE39A2497CACCAD41B0AE7A36E6F01FECA1B0B062F9C3232BD6F6734C97DB2EE7DE050370087210F8161B07237E712E29E0BA6B2B661EC22DAD0509D50EF75255D40B954D2B3694C30E5982EB2D15B72A8709BC4F9B6FEFC4E6F01FF04D128311209B4C4353AF43BDE58D631DB7E047D7B469CDE4A9176415F6F7B60BC129E0BA8363D77140DA0FC685DB76FCB968C8C58657DB86FC908E7D11F41D907CA367C17AD3EF81280721FD40E6AEDC118538AFE109B8FA818502982578BB2EAE3ACC3028EF79C4CAB575CC8473E05FF5D911186058BCA7F5269FAA66CBB68CE47EE0748F2A8E52EEB8475030D34E54C365572BA282226730ADA3BEF16D582A409F15AB89188F1737F7CA82EE0D96BD2B3A2153CDF6EE27FD04F4AEF4AA3FC6EB02E92EC21359864507EAD3E09D3117EEDD61AF34265412C362EE01B26B925BC74A2679A1C112C653AE88FD124220742A05DDCB1CDE7DF2342669FDF76E1C3F9BF8FE13633ADF33050FA491FA708A918ECA787FF074E90F9A0AA216ACB1160D22BE7A9817DEF5FA2E27FC906ACF5B5774A5DB069A66F5F1752D7521E1CE49F8888218BA24C97A92C287DAB8B08B4433EC1DDEB5C7A3D96956EBCC46A2DE2B95CACA91BECBCB15DCA20D13C977F50F6DC0D705DB78B597581F850E20E0EEB6EA9A7A9D2708A650A3CD0748EE1DDAEA559C80D2A00F2DBD29C3EF58C13CB0B84AFA30C508BC8AD71A4FC3E14690F16B52943013D7BB09F5627D7D1343DAED88198D5ADA23E0B94484E06A827FE49227540110ECEC9E354F87E27496B7FF5ED31521817C8B5B182129EE0C521C85A996BF5EAF1F87583BDFE69386EDCE63337252F58D37AEC742702D97F3B97CE0FD06EE60EE4380C2E48D7AAD49E26F76CE981237ADB617B0BB6D59DAE31B6335866E7F9670F8A25D9BC44D7C32602BA3D58002BD53473E3F781249AB17C1C78C496611#)
+   (q #00ED6FA36442A240865581B5C398C93F55AFD5386A801AF1D8BF8A9FF8AB9C578F97DA02AFC44180AB581A1E10A4D65B7D1E9C0B04D57FB1FD13B7ECF1AD7C0296F1F5D52C3223CB117E6C6BED1FDA701A7A4079FBD35B180D2295B216FEAB284F85594EDDDC179C9AAE6E6CA545D2FBCA308B0961B21A1B4A4E9DE27CF8D6922FC902C35313B05F0FFB5CC667E64E0706D5210D3919074B384CA5968359CBA5F4BCA096323AD48E2CDE9D25F08E2EF945A1DD46457F48B4BF8EBEC2DC3737CC09E333E30F17A3ACD19567A5C5D200E7A4303A97A893F0884ED3CD976B41C2BCEEF04D9FCE4F30218CC9AAE26FFC5749B10011B805ADA9C4857B691A7A820F2564DF5979F570F8D524DDE268F702891E9CCDBD9F821C2ECF27F60C795E743AC67CADB07D22D3A7BD322DA9C3E7A35AC6A35333F871FE6DE0162BA2A1F9565E411ADD424FF0727B2280BAFDDF522C272D3A2910EFE27F5590F8E0F6C38BEA1895A0893755F44BDF41FECF3BF3BA27C6D6F036A1AA70736DE71465FA78EA46F34341E05DE7AB37A5074B4E99E0BAB54D658629503E1242ACAFDE08721058F208D3F62FAE742D158CAE521994514CF0BEC580F075046319FBFC8E971F0FF5EED9E2AB7D194CD21ECCCB2E54D239AA3B0254F4AB1641E6E8E1512FF1D1ED579205F1807898828CFA24B80D230FA6C52C6A92348E1A069C239F7B6F2E7CC995BD3CD1B413FC86C626FE962C7EC3CF19FA193B3A732AF17E51E6B57CC263DE82A5C45CB9E37C2BAB44E88E5792FBBC40748F86134D221BF775E2BF57F98A884FCBA494718661A3FF73AAE04A6F5CDFB6F143D680D09BA4CCD6C6F186E92569B8F67A35B5796B9F2ED404F11C54ED290D7D7836501473F06D8A623D53AB586B644F3F5BDF8B0B670CA696A23B7B52319C91D2CA27FCDF421030CEA8A6B079FEC2E467BE0427AFCEA12648F3E12F09745166D20D3D1CD6965EABA2732469C077B3C4E44D3503D882937DA5139076144FAEAB75083BF4E16725BB9625A99DE92F6F226DE343F73E974577F8D6F5B57CFBEB60763627240A28C90A73BC7A9E47D74CBABB6486988C983A5A3DC91A4D8E353AE6C608499D21391E32D8EF3A030925959B52C1BC03F17AAE2FEE4E28B2B51889EC2A5FE587C60139C9CF29AF46555B089B54B5EF0BB3B92BCC0EA0E6E94F944FE8E9741DDA902F185E0D16876A10351AD22FE6ABD4378AD74A13CD2BD9696F0B59A069CD6582F92458A89B15648E833614598D6FDABAC026791CDB3FA87873CA86E3DF7187C9230AFFB1089EF83B8CB54856ADC234D07DF479514A8F47EC9903930E47F9E93AE1F96D7358F4208E19CCED8AEC063AFE8A31E39C921F7D7867D57E8F68B1494793E6DCD06F3DC68809BD455DBB44076D6AB64586E0A09A42CEBA2829A5F81BDEA5EDB23DC251D69C373D2E275201#)
+   (u #00EA2CBBEFBFFBD4BD3850584AEA315F88ED892F7398E5C4ECD17F8E4588B073DA32AC708DADC0E55417553FB4DC25130F42A9A04E435C63E1091744232D53FF98ABA450E3B91AF512631E28BF453BE4FCB9713112F890F368523FE175B0909385F0B404B3E6370FA6DB33490DC216CE3DE548FDDF68C81FE49BB9683C30FA6D1DE8B019A94683E508B720F2EDA20133325FD4644620D086182F1E8283215D2BCBBC1B302DEA714CE1E59FE8E996489018078F8CCDDAEF086EFDF82BA45DF424E539ABC9D61ABEC14346275AA9256031514AAC59FF40C7D1B4363AC7C74E8CC3854C9E57F6913C2CFC599E9DBB446D553482C9B531563A7CADD562D64151B3961FB52A0D542406D491F8090EBB737C388016F95918313C0EC987F701F2A25AE3F0CEF2B9F3460A9E48AEE382F01CB09B0A9372104FAC2EE692BB2B14E6FE376A29891687E157C40F09FB3283402E4D319C9791E7A06025C542B4411EEA71890D22E34E8038B3002AC7FB75A50ED29AAEAFF36588950A06A8D2139B0420673DCB37087E8196A034D0A5C78A824BCD0A74BBD7E08B04B8F08F473C09F6350508CEB476DEE1E41D0CF960CA3E87AE8489811577F7D49CB1EF885453F7087B8126FB99028B5771EC9E159040109102DEE175DAFA038EE7B62B96797E56E6361C37DFC42398020114765E28C3F3B4B6A4C33A86A995A0D5647068B7147552F4E6130866527D4833949E9F9204406F096735F33BFD1BB57734E15D0B4035A37CCA7C897C18162B12951A684F586F1B7FF041A85B7F44FAC125A80AC782AD3F4D7EC52C318EEA52CFA6AF09EBA50813B5BAC8367B1FF80A99DB8BDEC3E3842455A06D22DA99F0BE5B52330D1D5C0CCACB3661D703BE1D96E7832A159C8858E08CC23101FBC0DE783D3209A80A3ED4EBCF57661B01D84EBCFBE70A0EC921588B8CD9B9BF21918D86C3C97B0F6BBF4037E80C99A349A1A2B78F337CC4029415FF0DB54AC9A3A1DF7E07482DC9F04E638C9D5BBAAD32A627F2EF1DC3E17AEC365E416C703C449AA40104DEC358202F7F78CCF77115ADAD567CDAE6B4B2C81DA4FBE6A97BBF2A704389911E4A5B39C3C1F187101E53B3DF7A0CE05C4B7956F4ED31DD225B46036C5344B3CDB236E5B1A12E159008D106D1CF6C14C5F7335A4A5D80E008F0106F636EF750723B50511F37B3BA6FFBEB27A270828B9CB123D7F59EA0BE956C0D024C77AC06086460998F18610ECB94651DF47AB37DDDCDB9797203A4321CBC1E6E85EC64919EB74AC7E2F3C15FEB5DFCCFC2359D353C8B6B600152D4211A55477FF31026B34C10C5F1FC1A1DD1C1EF6A14B26CFD1AF70D6BAA4461B4387631E4DCFDFFAB118F710A8B8B2D12EEC4924751720B9AA9D94527B9F19E8B352222567F662FC6753AA4BE22C2A851F2378AD5EE5539C1E0F4DD90400DD7DC6F1EA675D9#)
+   )
+  )
+ )