From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 103300 invoked by alias); 28 Feb 2020 13:03:18 -0000 Mailing-List: contact cygwin-apps-cvs-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Post: List-Help: , Sender: cygwin-apps-cvs-owner@sourceware.org Received: (qmail 103215 invoked by uid 9795); 28 Feb 2020 13:03:18 -0000 Date: Fri, 28 Feb 2020 13:03:00 -0000 Message-ID: <20200228130318.103204.qmail@sourceware.org> From: jturney@sourceware.org To: cygwin-apps-cvs@sourceware.org Subject: [setup - the official Cygwin setup program] branch master, updated. release_2.902 X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Oldrev: 0706ce81165537f56814379b24290640d441de51 X-Git-Newrev: 9096650dcc557ef5a63610b4aeb8c9dfd4296cee X-SW-Source: 2020-q1/txt/msg00026.txt https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=9096650dcc557ef5a63610b4aeb8c9dfd4296cee commit 9096650dcc557ef5a63610b4aeb8c9dfd4296cee Author: Jon Turney Date: Fri Feb 21 19:54:13 2020 +0000 setup.ini signing key rotation Verify the setup.ini signature using a new key, or the old key (unless the --disable-old-keys option is used) New key: sec 4096R/1A698DE9E2E56300 2020-02-27 [expires: 2022-02-26] Key fingerprint = 5640 5CF6 FCC8 1574 682A 5D56 1A69 8DE9 E2E5 6300 uid Cygwin Old key: sec 1024D/A9A262FF676041BA 2008-06-13 Key fingerprint = 1169 DF9F 2273 4F74 3AA5 9232 A9A2 62FF 6760 41BA uid Cygwin ssb 1024g/0AF098B5A1DB7B5C 2008-06-13 Export key from gpg and generate s-expr format using: $ gpg --export KEYID >cygwin.pub $ ./gpg-key-to-s-expr.sh -C ./cygwin.pub >cyg-pubkey.h https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=5f4250b8ecdbea038980e8b32b8b7a7fc7df0827 commit 5f4250b8ecdbea038980e8b32b8b7a7fc7df0827 Author: Jon Turney Date: Tue Feb 25 13:49:31 2020 +0000 Handle multiple signature packets in .sig file Rather than stopping after the first signature packet, handle multiple signature packets appearing in a .sig file. If any of them is a valid signature from a known key, then the signature is good. https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=37b70a874b527beba4352503128f4c83ffeeeb1d commit 37b70a874b527beba4352503128f4c83ffeeeb1d Author: Jon Turney Date: Mon Feb 24 14:47:41 2020 +0000 Provide hash to DSA as an opaque block Provide the hash to DSA as an opaque data block, rather than an MPI. This allows libgcrypt to truncate the hash correctly when a hash alg is specified which produces more than the 160 bits DSA accepts. https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=8d72e78e97fe3e1ad33e9d351db3fa30caceac03 commit 8d72e78e97fe3e1ad33e9d351db3fa30caceac03 Author: Jon Turney Date: Sun Feb 23 21:13:26 2020 +0000 Increase buffer size in LogPrintf adaptors crypto.cc can now generate output lines bigger than the current buffer size https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=8eb0b0ebb86e6d58dcbfa7099c437cff7e7f5ff7 commit 8eb0b0ebb86e6d58dcbfa7099c437cff7e7f5ff7 Author: Jon Turney Date: Sat Feb 22 20:44:35 2020 +0000 Various improvements to debug output from crypto.cc Build an adaptor for log output from gcrypt (unfortunately complex as it needs to gather/split lines on '\n') Don't really need a hard breakpoint for ERRKIND, so it's the same, irrespective of CRYPTODEBUGGING being defined. Always debug log the pk type and hash alg in use. Allow CRYPTODEBUGGING to be set by CPPFLAGS etc. https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=03c0b4ad2fa579d513ccea30b7391f9ab9237639 commit 03c0b4ad2fa579d513ccea30b7391f9ab9237639 Author: Jon Turney Date: Sun Feb 23 18:38:18 2020 +0000 Teach --pubkey option to handle RSA keys v2: Fix conflicting use of variable 'n' v3: Release RSA mpi temps 'n' and 'e' https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=dd9d95a31461a0bb6114d07ea95a057c925890cb commit dd9d95a31461a0bb6114d07ea95a057c925890cb Author: Jon Turney Date: Sat Feb 22 20:05:14 2020 +0000 Teach gpg-key-to-s-expr.sh to handle RSA pubkeys https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=28d401b5407ff09833e33b23de5d2c52489ad1fb commit 28d401b5407ff09833e33b23de5d2c52489ad1fb Author: Jon Turney Date: Sun Feb 23 23:47:37 2020 +0000 Add support for RSA key signatures v2: Fix leak of rsa_mpi_s https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=1211705be1a347ea1ca0fa8d0b4c9bbddf09e741 commit 1211705be1a347ea1ca0fa8d0b4c9bbddf09e741 Author: Jon Turney Date: Sat Feb 22 20:23:46 2020 +0000 Convert msg under CRYPTODEBUGGING to LogBabblePrintf Convert msg under CRYPTODEBUGGING to LogBabblePrintf, missed in 6f2a7375. https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=98d3e45a148d9380231aadb3ca8f239d2a6c86d3 commit 98d3e45a148d9380231aadb3ca8f239d2a6c86d3 Author: Jon Turney Date: Fri Feb 21 19:07:02 2020 +0000 Restructure how we try keys in order for signature checking Restructure how we try keys in order for signature checking, so we can log which key signature was made by v2: Correctly default _owned to true https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=ef76a57fa05ce86710a0633986b7d5147485f386 commit ef76a57fa05ce86710a0633986b7d5147485f386 Author: Jon Turney Date: Fri Feb 21 18:24:51 2020 +0000 Remove gpg-error-config-fake Unused since 1e0f90f9 Diff: --- LogSingleton.cc | 8 +- crypto.cc | 576 ++++++++++++++++++++++++++++++++------------------ crypto.h | 5 +- cyg-old-pubkey.h | 14 ++ cyg-pubkey.h | 10 +- cygwin-old.pub | Bin 0 -> 964 bytes cygwin.pub | Bin 964 -> 1205 bytes gpg-error-config-fake | 57 ----- gpg-key-to-s-expr.sh | 49 +++-- 9 files changed, 424 insertions(+), 295 deletions(-) diff --git a/LogSingleton.cc b/LogSingleton.cc index 73b8fb61..e08717f1 100644 --- a/LogSingleton.cc +++ b/LogSingleton.cc @@ -81,10 +81,10 @@ void LogBabblePrintf(const char *fmt, ...) { int len; - char buf[2000]; + char buf[8192]; va_list args; va_start (args, fmt); - len = vsnprintf (buf, 2000, fmt, args); + len = vsnprintf (buf, 8192, fmt, args); if ((len > 0) && (buf[len-1] == '\n')) buf[len-1] = 0; Log (LOG_BABBLE) << buf << endLog; @@ -94,10 +94,10 @@ void LogPlainPrintf(const char *fmt, ...) { int len; - char buf[2000]; + char buf[8192]; va_list args; va_start (args, fmt); - len = vsnprintf (buf, 2000, fmt, args); + len = vsnprintf (buf, 8192, fmt, args); if ((len > 0) && (buf[len-1] == '\n')) buf[len-1] = 0; Log (LOG_PLAIN) << buf << endLog; diff --git a/crypto.cc b/crypto.cc index 5a10e165..597a2a98 100644 --- a/crypto.cc +++ b/crypto.cc @@ -30,47 +30,78 @@ #include "gpg-packet.h" #include "geturl.h" +#ifndef CRYPTODEBUGGING #define CRYPTODEBUGGING (0) +#endif +#define ERRKIND note #if CRYPTODEBUGGING -#define ERRKIND __asm__ __volatile__ (".byte 0xcc"); note #define MESSAGE LogBabblePrintf -#else /* !CRYPTODEBUGGING */ -#define ERRKIND note +#else #define MESSAGE while (0) LogBabblePrintf -#endif /* CRYPTODEBUGGING */ +#endif /* Command-line options for specifying and controlling extra keys. */ static StringArrayOption ExtraKeyOption ('K', "pubkey", - "URL of extra public key file (gpg format)"); + "URL or absolute path of extra public key file (RFC4880 format)"); static StringArrayOption SexprExtraKeyOption ('S', "sexpr-pubkey", - "Extra public key in s-expr format"); + "Extra DSA public key in s-expr format"); static BoolOption UntrustedKeysOption (false, 'u', "untrusted-keys", "Use untrusted saved extra keys"); static BoolOption KeepUntrustedKeysOption (false, 'U', "keep-untrusted-keys", "Use untrusted keys and retain all"); +static BoolOption DisableOldKeysOption (false, '\0', "disable-old-keys", + "Disable old cygwin.com keys"); -/* Embedded public half of Cygwin DSA signing key. */ -static const char *cygwin_pubkey_sexpr = +/* Embedded public half of Cygwin signing key. */ +static const char *cygwin_pubkey_sexpr = #include "cyg-pubkey.h" ; +static const char *cygwin_old_pubkey_sexpr = +#include "cyg-old-pubkey.h" +; + /* S-expr template for DSA pubkey. */ static const char *dsa_pubkey_templ = "(public-key (dsa (p %m) (q %m) (g %m) (y %m)))"; +/* S-expr template for RSA pubkey. */ +static const char *rsa_pubkey_templ = "(public-key (rsa (n %m) (e %m)))"; + /* S-expr template for DSA signature. */ static const char *dsa_sig_templ = "(sig-val (dsa (r %m) (s %m)))"; -/* S-expr template for data block to be signed. */ -static const char *data_hash_templ = "(data (flags raw) (value %m))"; +/* S-expr template for RSA signature. */ +static const char *rsa_sig_templ = "(sig-val (rsa (s %m)))"; + +/* S-expr template for DSA data block to be signed. */ +static const char *dsa_data_hash_templ = "(data (flags raw) (hash %s %b))"; + +/* S-expr template for RSA data block to be signed. */ +static const char *rsa_data_hash_templ = "(data (flags pkcs1) (hash %s %b))"; + +/* Information on a key to try */ +struct key_info +{ + key_info(std::string _name, bool _builtin, gcry_sexp_t _key, bool _owned=true) : + name(_name), builtin(_builtin), key(_key), owned(_owned) + { + } + + std::string name; + bool builtin; // if true, we don't need to retain this key with add_key_from_sexpr() + gcry_sexp_t key; + bool owned; // if true, we own this key and should use gcry_sexp_release() on it +}; /* User context data for sig packet walk. */ struct sig_data { /* MPI values of sig components. */ gcry_mpi_t dsa_mpi_r, dsa_mpi_s; + gcry_mpi_t rsa_mpi_s; /* Hash context. */ gcry_md_hd_t md; @@ -86,8 +117,11 @@ struct sig_data /* Converted algo code. */ int algo; + /* Keys */ + std::vector *keys_to_try; + /* Final status. */ - bool complete; + bool valid; }; /* User context data for key packet walk. */ @@ -97,17 +131,17 @@ struct key_data }; /* Callback hook for walking packets in gpg key file. Extracts - the DSA coefficients from any public key packets encountered and + the key coefficients from any public key packets encountered and converts them into s-expr pubkey format, returning the public keys thus found to the caller in a vector in the userdata context. */ static enum pkt_cb_resp key_file_walker (struct packet_walker *wlk, unsigned char tag, - size_t packetsize, size_t hdrpos) + size_t packetsize, size_t hdrpos) { struct key_data *kdat = (struct key_data *)(wlk->userdata); MESSAGE ("key packet %d size %d at offs $%04x kdat $%08x\n", tag, - packetsize, hdrpos, kdat); + packetsize, hdrpos, kdat); if (tag != RFC4880_PT_PUBLIC_KEY) return pktCONTINUE; @@ -129,54 +163,77 @@ pkt_cb_resp key_file_walker (struct packet_walker *wlk, unsigned char tag, } char pkalg = pkt_getch (wlk->pfile); - if (pkalg != RFC4880_PK_DSA) + if ((pkalg != RFC4880_PK_DSA) && (pkalg != RFC4880_PK_RSA)) { ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, pkalg, "unsupported key alg."); return pktCONTINUE; } - // Next, the four MPIs should be present. Read them out, - // convert to an s-expr and add that to the list. - gcry_mpi_t p, q, g, y; - p = q = g = y = 0; + // Next, the key coefficient MPIs should be present. Read them out, convert + // to an s-expr and add that to the list of keys. + size_t erroff; + gcry_sexp_t new_key; - if ((pkt_get_mpi (&p, wlk->pfile) >= 0) - && (pkt_get_mpi (&q, wlk->pfile) >= 0) - && (pkt_get_mpi (&g, wlk->pfile) >= 0) - && (pkt_get_mpi (&y, wlk->pfile) >= 0)) + if (pkalg == RFC4880_PK_DSA) { - // Convert to s-expr. - gcry_sexp_t new_key; - size_t n; - - gcry_error_t rv = gcry_sexp_build (&new_key, &n, dsa_pubkey_templ, p, q, g, y); - if (rv != GPG_ERR_NO_ERROR) - { - ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, rv, "while creating sig s-expr."); - return pktCONTINUE; - } + gcry_mpi_t p, q, g, y; + p = q = g = y = 0; + + if ((pkt_get_mpi (&p, wlk->pfile) >= 0) + && (pkt_get_mpi (&q, wlk->pfile) >= 0) + && (pkt_get_mpi (&g, wlk->pfile) >= 0) + && (pkt_get_mpi (&y, wlk->pfile) >= 0)) + { + gcry_error_t rv = gcry_sexp_build (&new_key, &erroff, dsa_pubkey_templ, p, q, g, y); + if (rv != GPG_ERR_NO_ERROR) + { + ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, rv, "while creating sig s-expr."); + return pktCONTINUE; + } + } + + // Release temps and continue. + if (p) + gcry_mpi_release (p); + if (q) + gcry_mpi_release (q); + if (g) + gcry_mpi_release (g); + if (y) + gcry_mpi_release (y); + } + else if (pkalg == RFC4880_PK_RSA) + { + gcry_mpi_t n, e; + n = e = 0; + + if ((pkt_get_mpi (&n, wlk->pfile) >= 0) + && (pkt_get_mpi (&e, wlk->pfile) >= 0)) + { + gcry_error_t rv = gcry_sexp_build (&new_key, &erroff, rsa_pubkey_templ, n, e); + if (rv != GPG_ERR_NO_ERROR) + { + ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, rv, "while creating sig s-expr."); + return pktCONTINUE; + } + } + + if (n) + gcry_mpi_release (n); + if (e) + gcry_mpi_release (e); + } #if CRYPTODEBUGGING - // Debugging - char sexprbuf[GPG_KEY_SEXPR_BUF_SIZE]; - n = gcry_sexp_sprint (new_key, GCRYSEXP_FMT_ADVANCED, sexprbuf, - GPG_KEY_SEXPR_BUF_SIZE); - msg ("key:%d\n'%s'", n, sexprbuf); + // Debugging + char sexprbuf[GPG_KEY_SEXPR_BUF_SIZE]; + n = gcry_sexp_sprint (new_key, GCRYSEXP_FMT_ADVANCED, sexprbuf, + GPG_KEY_SEXPR_BUF_SIZE); + LogBabblePrintf ("key:%d\n'%s'", n, sexprbuf); #endif /* CRYPTODEBUGGING */ - // Return it to caller in the vector. - kdat->keys.push_back (new_key); - } - - // Release temps and continue. - if (p) - gcry_mpi_release (p); - if (q) - gcry_mpi_release (q); - if (g) - gcry_mpi_release (g); - if (y) - gcry_mpi_release (y); + // Return it to caller in the vector. + kdat->keys.push_back (new_key); return pktCONTINUE; } @@ -230,6 +287,114 @@ fold_lfs_and_spaces (char *buf, size_t n) return ptr2 - buf; } +/* Size and allocate a temp buffer to print a representation + of a public key s-expr into, then add that to the extra keys + setting so it persists for the next run. */ +static void +add_key_from_sexpr (gcry_sexp_t key) +{ + size_t n = gcry_sexp_sprint (key, GCRYSEXP_FMT_ADVANCED, 0, ~0); + char *sexprbuf = new char[n]; + n = gcry_sexp_sprint (key, GCRYSEXP_FMT_ADVANCED, sexprbuf, n); + // +1 because we want to include the nul-terminator. + n = fold_lfs_and_spaces (sexprbuf, n + 1); + ExtraKeysSetting::instance().add_key (sexprbuf); + MESSAGE ("keep:%d\n'%s'", n, sexprbuf); + delete [] sexprbuf; +} + +static bool +verify_sig(struct sig_data *sigdat, HWND owner) +{ + gcry_error_t rv; + size_t n; + { + /* sig coefficients in s-expr format. */ + gcry_sexp_t sig; + + /* signature hash data in s-expr format. */ + gcry_sexp_t hash; + + /* Build everything into s-exprs, and call the libgcrypt verification + routine. */ + + if (sigdat->pk_alg == RFC4880_PK_DSA) + { + rv = gcry_sexp_build (&sig, &n, dsa_sig_templ, sigdat->dsa_mpi_r, + sigdat->dsa_mpi_s); + if (rv != GPG_ERR_NO_ERROR) + { + ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating sig s-expr."); + return false; + } + + rv = gcry_sexp_build (&hash, &n, dsa_data_hash_templ, + gcry_md_algo_name(sigdat->algo), + gcry_md_get_algo_dlen (sigdat->algo), + gcry_md_read (sigdat->md, 0)); + if (rv != GPG_ERR_NO_ERROR) + { + ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating hash s-expr."); + return false; + } + } + else if (sigdat->pk_alg == RFC4880_PK_RSA) + { + rv = gcry_sexp_build (&sig, &n, rsa_sig_templ, sigdat->rsa_mpi_s); + if (rv != GPG_ERR_NO_ERROR) + { + ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating sig s-expr."); + return false; + } + + rv = gcry_sexp_build (&hash, &n, rsa_data_hash_templ, + gcry_md_algo_name(sigdat->algo), + gcry_md_get_algo_dlen (sigdat->algo), + gcry_md_read (sigdat->md, 0)); + if (rv != GPG_ERR_NO_ERROR) + { + ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating hash s-expr."); + return false; + } + } + +#if CRYPTODEBUGGING + n = gcry_sexp_sprint (sig, GCRYSEXP_FMT_ADVANCED, sexprbuf, + GPG_KEY_SEXPR_BUF_SIZE); + LogBabblePrintf ("sig:%d\n'%s'", n, sexprbuf); + n = gcry_sexp_sprint (hash, GCRYSEXP_FMT_ADVANCED, sexprbuf, + GPG_KEY_SEXPR_BUF_SIZE); + LogBabblePrintf ("hash:%d\n'%s'", n, sexprbuf); +#endif /* CRYPTODEBUGGING */ + + // Well, we're actually there! + // Try it against each key in turn + + std::vector::iterator it; + for (it = sigdat->keys_to_try->begin (); + it < sigdat->keys_to_try->end (); + ++it) + { + rv = gcry_pk_verify (sig, hash, it->key); + + LogBabblePrintf("signature: tried key %s, returned 0x%08x %s\n", + it->name.c_str(), rv, gcry_strerror(rv)); + + if (rv != GPG_ERR_NO_ERROR) + continue; + // Found it! This key gets kept! + if (!it->builtin) + add_key_from_sexpr (it->key); + break; + } + + gcry_sexp_release (sig); + gcry_sexp_release (hash); + } + + return (rv == GPG_ERR_NO_ERROR); +} + /* Do-nothing stubs called by the sig file walker to walk over the embedded subpackets. In the event, we don't actually need to do this as we aren't inspecting them. */ @@ -258,7 +423,6 @@ pkt_cb_resp sig_file_walker (struct packet_walker *wlk, unsigned char tag, size_t packetsize, size_t hdrpos) { struct sig_data *sigdat = (struct sig_data *)(wlk->userdata); - sigdat->complete = false; if (tag != RFC4880_PT_SIGNATURE) return pktCONTINUE; @@ -306,8 +470,8 @@ pkt_cb_resp sig_file_walker (struct packet_walker *wlk, unsigned char tag, sigdat->hash_alg = pkt_getch (wlk->pfile); } - MESSAGE ("sig type %d, pk_alg %d, hash_alg %d\n", sigdat->sig_type, - sigdat->pk_alg, sigdat->hash_alg); + LogBabblePrintf("signature: sig_type %d, pk_alg %d, hash_alg %d\n", + sigdat->sig_type, sigdat->pk_alg, sigdat->hash_alg); // We only handle binary file signatures if (sigdat->sig_type != RFC4880_ST_BINARY) @@ -315,8 +479,9 @@ pkt_cb_resp sig_file_walker (struct packet_walker *wlk, unsigned char tag, ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, sigdat->sig_type, "unsupported sig type."); return pktHALT; } - // And we only speak DSA. - if (sigdat->pk_alg != RFC4880_PK_DSA) + + // We only handle RSA and DSA keys + if ((sigdat->pk_alg != RFC4880_PK_DSA) && (sigdat->pk_alg != RFC4880_PK_RSA)) { ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, sigdat->pk_alg, "unsupported pk alg."); return pktHALT; @@ -331,6 +496,7 @@ pkt_cb_resp sig_file_walker (struct packet_walker *wlk, unsigned char tag, } // Now we know hash algo, we can create md context. + sigdat->md = 0; gcry_error_t rv = gcry_md_open (&sigdat->md, sigdat->algo, 0); if (rv != GPG_ERR_NO_ERROR) { @@ -363,23 +529,39 @@ pkt_cb_resp sig_file_walker (struct packet_walker *wlk, unsigned char tag, // Both formats now have 16 bits of the hash value. int hash_first = pkt_getword (wlk->pfile); - MESSAGE ("sig type %d, pk_alg %d, hash_alg %d - first $%04x\n", sigdat->sig_type, - sigdat->pk_alg, sigdat->hash_alg, hash_first); + MESSAGE ("signature: hash leftmost 2 bytes 0x%04x\n", hash_first); - /* Algorithm-Specific Fields for DSA signatures: + /* Algorithm-Specific Fields for signatures: - - MPI of DSA value r. + for DSA: + - MPI of DSA value r + - MPI of DSA value s - - MPI of DSA value s. + DSA signatures MUST use hashes that are equal in size to the number of + bits of q, the group generated by the DSA key's generator value. - DSA signatures MUST use hashes that are equal in size to the number - of bits of q, the group generated by the DSA key's generator value. */ + for RSA: + - MPI of RSA value m^d mod n (aka s) + */ + sigdat->dsa_mpi_r = sigdat->dsa_mpi_s = 0; + sigdat->rsa_mpi_s = 0; - if ((pkt_get_mpi (&sigdat->dsa_mpi_r, wlk->pfile) < 0) - || (pkt_get_mpi (&sigdat->dsa_mpi_s, wlk->pfile) < 0)) + if (sigdat->pk_alg == RFC4880_PK_DSA) { - ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, "unpacking mpi."); - return pktHALT; + if ((pkt_get_mpi (&sigdat->dsa_mpi_r, wlk->pfile) < 0) + || (pkt_get_mpi (&sigdat->dsa_mpi_s, wlk->pfile) < 0)) + { + ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, "unpacking mpi."); + return pktHALT; + } + } + else if (sigdat->pk_alg == RFC4880_PK_RSA) + { + if (pkt_get_mpi (&sigdat->rsa_mpi_s, wlk->pfile) < 0) + { + ERRKIND (wlk->owner, IDS_CRYPTO_ERROR, "unpacking mpi."); + return pktHALT; + } } MESSAGE ("Read sig packets succesfully!\n"); @@ -405,43 +587,80 @@ pkt_cb_resp sig_file_walker (struct packet_walker *wlk, unsigned char tag, gcry_md_putc (sigdat->md, nbytes & 0xff); } - // Hooray, succeeded! - sigdat->complete = true; - - return pktHALT; + // finalize the hash + gcry_md_final (sigdat->md); + MESSAGE("digest length is %d\n",gcry_md_get_algo_dlen (sigdat->algo)); + + // we have hashed all the data, and found the sig coefficients. + // heck this signature + if (verify_sig (sigdat, wlk->owner)) + sigdat->valid = true; + + // discard hash + if (sigdat->md) + gcry_md_close (sigdat->md); + + // discard sig coefffcients + if (sigdat->dsa_mpi_r) + gcry_mpi_release (sigdat->dsa_mpi_r); + if (sigdat->dsa_mpi_s) + gcry_mpi_release (sigdat->dsa_mpi_s); + if (sigdat->rsa_mpi_s) + gcry_mpi_release (sigdat->rsa_mpi_s); + + // we can stop immediately if we found a good signature + return sigdat->valid ? pktHALT : pktCONTINUE; } -/* Size and allocate a temp buffer to print a representation - of a public key s-expr into, then add that to the extra keys - setting so it persists for the next run. */ -void -add_key_from_sexpr (gcry_sexp_t key) +#if CRYPTODEBUGGING +static void +gcrypt_log_adaptor(void *priv, int level, const char *fmt, va_list args) { - size_t n = gcry_sexp_sprint (key, GCRYSEXP_FMT_ADVANCED, 0, ~0); - char *sexprbuf = new char[n]; - n = gcry_sexp_sprint (key, GCRYSEXP_FMT_ADVANCED, sexprbuf, n); - // +1 because we want to include the nul-terminator. - n = fold_lfs_and_spaces (sexprbuf, n + 1); - ExtraKeysSetting::instance().add_key (sexprbuf); - MESSAGE ("keep:%d\n'%s'", n, sexprbuf); - delete [] sexprbuf; + static std::string collected; + + char buf[GPG_KEY_SEXPR_BUF_SIZE]; + vsnprintf (buf, GPG_KEY_SEXPR_BUF_SIZE, fmt, args); + + char *start = buf; + char *end; + + do + { + if (collected.length() == 0) + { + collected = "gcrypt: "; + } + + end = strchr(start, '\n'); + if (end) + *end = '\0'; + + collected += start; + + if (end) + { + if (level == GCRY_LOG_DEBUG) + Log (LOG_BABBLE) << collected << endLog; + else + Log (LOG_PLAIN) << collected << endLog; + + collected.clear(); + start = end + 1; + } + } + while (end); } +#endif /* Verify the signature on an ini file. Takes care of all key-handling. */ bool verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, HWND owner) { - /* DSA public key in s-expr format. */ - gcry_sexp_t dsa_key; - /* Data returned from packet walker. */ struct sig_data sigdat; - /* Vector of extra keys to use. */ - std::vector keys_to_try; - - /* Vector of cached extra keys from last run. */ - static std::vector input_keys; + /* Vector of keys to use. */ + std::vector keys_to_try; /* Overall status of signature. */ bool sig_ok = false; @@ -451,21 +670,52 @@ verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, HWND owner) size_t n; /* Initialise the library. */ - gcry_check_version (NULL); + static bool gcrypt_init = false; + if (!gcrypt_init) + { +#if CRYPTODEBUGGING + gcry_set_log_handler (gcrypt_log_adaptor, NULL); + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); +#endif + gcry_check_version (NULL); + gcrypt_init = true; + } /* So first build the built-in key. */ - rv = gcry_sexp_new (&dsa_key, cygwin_pubkey_sexpr, strlen (cygwin_pubkey_sexpr), 1); + gcry_sexp_t cygwin_key; + rv = gcry_sexp_new (&cygwin_key, cygwin_pubkey_sexpr, strlen (cygwin_pubkey_sexpr), 1); if (rv != GPG_ERR_NO_ERROR) { ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating pubkey s-expr."); } + else + { + keys_to_try.push_back (key_info("cygwin", true, cygwin_key)); + } #if CRYPTODEBUGGING char sexprbuf[GPG_KEY_SEXPR_BUF_SIZE]; - n = gcry_sexp_sprint (dsa_key, GCRYSEXP_FMT_ADVANCED, sexprbuf, GPG_KEY_SEXPR_BUF_SIZE); - msg ("key:%d\n'%s'", n, sexprbuf); + n = gcry_sexp_sprint (cygwin_key, GCRYSEXP_FMT_ADVANCED, sexprbuf, GPG_KEY_SEXPR_BUF_SIZE); + LogBabblePrintf ("key:%d\n'%s'", n, sexprbuf); #endif /* CRYPTODEBUGGING */ + /* If not disabled, also try the old built-in key */ + gcry_sexp_t cygwin_old_key; + if (!DisableOldKeysOption) + { + rv = gcry_sexp_new (&cygwin_old_key, cygwin_old_pubkey_sexpr, strlen (cygwin_old_pubkey_sexpr), 1); + if (rv != GPG_ERR_NO_ERROR) + { + ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating old pubkey s-expr."); + } + else + { + keys_to_try.push_back (key_info ("cygwin-old", TRUE, cygwin_old_key)); + } + } + /* Vector of cached extra keys from last run. */ + static std::vector input_keys; + /* Next we should extract the keys from the extrakeys user setting, and flush it; we'll only return them to it if they get used. OTOH, should we do this at all? The user settings @@ -504,6 +754,15 @@ verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, HWND owner) } } + // We only use the untrusted keys if told to. + if (KeepUntrustedKeysOption || UntrustedKeysOption) + for (std::vector::const_iterator it = input_keys.begin (); + it < input_keys.end (); + ++it) + { + keys_to_try.push_back (key_info ("saved key", false, *it, false)); + } + /* Next, there may have been command-line options. */ std::vector SexprExtraKeyStrings = SexprExtraKeyOption; for (std::vector::const_iterator it @@ -525,9 +784,9 @@ verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, HWND owner) // +1 because we want to include the nul-terminator. n = fold_lfs_and_spaces (sexprbuf, n + 1); ExtraKeysSetting::instance().add_key (sexprbuf); - msg ("key2:%d\n'%s'", n, sexprbuf); + LogBabblePrintf ("key2:%d\n'%s'", n, sexprbuf); #endif /* CRYPTODEBUGGING */ - keys_to_try.push_back (dsa_key2); + keys_to_try.push_back (key_info ("from command-line option --sexpr-pubkey", false, dsa_key2)); } else { @@ -560,9 +819,9 @@ verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, HWND owner) // +1 because we want to include the nul-terminator. n = fold_lfs_and_spaces (sexprbuf, n + 1); ExtraKeysSetting::instance().add_key (sexprbuf); - msg ("key3:%d\n'%s'", n, sexprbuf); + LogBabblePrintf ("key3:%d\n'%s'", n, sexprbuf); #endif /* CRYPTODEBUGGING */ - keys_to_try.push_back (kdat.keys.back ()); + keys_to_try.push_back (key_info ("from command-line option --pubkey", false, kdat.keys.back ())); kdat.keys.pop_back (); } } @@ -571,124 +830,21 @@ verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, HWND owner) // We pass in a pointer to the ini file in the user context data, // which the packet walker callback uses to create a new hash // context preloaded with all the signature-covered data. - sigdat.complete = false; + sigdat.valid = false; sigdat.sign_data = ini_file; - sigdat.dsa_mpi_r = sigdat.dsa_mpi_s = 0; - sigdat.md = 0; - pkt_walk_packets (ini_sig_file, sig_file_walker, owner, 0, - ini_sig_file->get_size (), &sigdat); - if (sigdat.complete) - { - /* DSA sig coefficients in s-expr format. */ - gcry_sexp_t dsa_sig; - - /* DSA signature hash data in s-expr format. */ - gcry_sexp_t dsa_hash; + sigdat.keys_to_try = &keys_to_try; - /* So, we have hashed all the data, and found the sig coefficients. - Next stages are to finalise the hash, build everything into - s-exprs, and call the libgcrypt verification routine. */ - - rv = gcry_sexp_build (&dsa_sig, &n, dsa_sig_templ, sigdat.dsa_mpi_r, - sigdat.dsa_mpi_s); - if (rv != GPG_ERR_NO_ERROR) - { - ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating sig s-expr."); - return false; - } - - gcry_md_final (sigdat.md); - - // Make a temp mpi from the hash output, then an s-expr from that. - gcry_mpi_t dsa_mpi_hash = 0; - unsigned char *tmpbuf = gcry_md_read (sigdat.md, 0); - size_t dlen = gcry_md_get_algo_dlen (sigdat.algo); - rv = gcry_mpi_scan (&dsa_mpi_hash, GCRYMPI_FMT_USG, tmpbuf, dlen, 0UL); - if (rv != GPG_ERR_NO_ERROR) - { - ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating hash MPI."); - return false; - } - - rv = gcry_sexp_build (&dsa_hash, &n, data_hash_templ, dsa_mpi_hash); - if (rv != GPG_ERR_NO_ERROR) - { - ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating hash s-expr."); - return false; - } - -#if CRYPTODEBUGGING - n = gcry_sexp_sprint (dsa_sig, GCRYSEXP_FMT_ADVANCED, sexprbuf, - GPG_KEY_SEXPR_BUF_SIZE); - msg ("sig:%d\n'%s'", n, sexprbuf); - n = gcry_sexp_sprint (dsa_hash, GCRYSEXP_FMT_ADVANCED, sexprbuf, - GPG_KEY_SEXPR_BUF_SIZE); - msg ("hash:%d\n'%s'", n, sexprbuf); -#endif /* CRYPTODEBUGGING */ - - // Well, we're actually there! Try it against the main key. - rv = gcry_pk_verify (dsa_sig, dsa_hash, dsa_key); - // If not that, try any supplied on the commandline. - if (rv != GPG_ERR_NO_ERROR) - { - std::vector::iterator it; - for (it = keys_to_try.begin (); it < keys_to_try.end (); ++it) - { - MESSAGE ("Testing a key to try\n"); - rv = gcry_pk_verify (dsa_sig, dsa_hash, *it); - if (rv != GPG_ERR_NO_ERROR) - continue; - // Found it! This key gets kept! - add_key_from_sexpr (*it); - break; - } - - // We only use the untrusted keys if told to. - it = ((rv != GPG_ERR_NO_ERROR) - && (KeepUntrustedKeysOption || UntrustedKeysOption)) - ? input_keys.begin () - : input_keys.end (); - for ( ; it < input_keys.end (); ++it) - { - MESSAGE ("Testing an input key\n"); - rv = gcry_pk_verify (dsa_sig, dsa_hash, *it); - if (rv != GPG_ERR_NO_ERROR) - continue; - // Found it! This key gets kept! - add_key_from_sexpr (*it); - break; - } - } - - sig_ok = (rv == GPG_ERR_NO_ERROR); - -#if CRYPTODEBUGGING - gcry_err_code_t code; - gcry_err_source_t src; - code = gcry_err_code (rv); - src = gcry_err_source (rv); - msg ("Well, pk verify returned $%08x - code %d src %d\n", rv, code, src); -#endif /* CRYPTODEBUGGING */ + pkt_walk_packets (ini_sig_file, sig_file_walker, owner, 0, + ini_sig_file->get_size (), &sigdat); - gcry_mpi_release (dsa_mpi_hash); - gcry_sexp_release (dsa_sig); - gcry_sexp_release (dsa_hash); - } + sig_ok = sigdat.valid; - // Discard the temp data then. - gcry_sexp_release (dsa_key); - if (sigdat.dsa_mpi_r) - gcry_mpi_release (sigdat.dsa_mpi_r); - if (sigdat.dsa_mpi_s) - gcry_mpi_release (sigdat.dsa_mpi_s); - if (sigdat.md) - gcry_md_close (sigdat.md); while (keys_to_try.size ()) { - gcry_sexp_release (keys_to_try.back ()); + if (keys_to_try.back ().owned) + gcry_sexp_release (keys_to_try.back ().key); keys_to_try.pop_back (); } return sig_ok; } - diff --git a/crypto.h b/crypto.h index 860df6c7..661d86da 100644 --- a/crypto.h +++ b/crypto.h @@ -308,12 +308,9 @@ extern bool verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, H */ -// Big enough to dump the coefficients of a DSA +// Big enough to dump the coefficients of a // signing key of any reasonable size in ASCII // s-expr representation. #define GPG_KEY_SEXPR_BUF_SIZE (8192) -// As long as you respect this maximum coefficient size. -#define GPG_KEY_MAX_COEFF_SIZE (8192) - #endif /* SETUP_CRYPTO_H */ diff --git a/cyg-old-pubkey.h b/cyg-old-pubkey.h new file mode 100644 index 00000000..6d79544a --- /dev/null +++ b/cyg-old-pubkey.h @@ -0,0 +1,14 @@ + +/* Autogenerated from: ./cygwin.pub + * by: ./gpg-key-to-s-expr.sh + * at: Mon Jun 16 02:20:11 2008 */ + +"(public-key " + "(dsa " + "(p #00b96e7de7db21b47aa365a60fc3ec39195d07c550164dd43e2c2ff36c5ca21242403716c8937a70d80cc142cb73498820dc8a1269acfdb1b3815cdb93047262788fd5fdddb095e03b47bf6daa3b55d61a4c6bfd74096716265311617b304e09977ec178abd22cc8b06821475f9e8ba8bebcbe26458cbcc293022b07fc5f4a91cb#) " + "(q #00d65d896bdd4fb133ba8ad55400260cbca3450871#) " + "(g #17a7506fa4611721f581ee0f01bd2f19218304846743481948192c73181d90e5716051a15eae3dca9ada22acf2fbc010d31c196aa8f9fb91c9c190ebece5167dbd2281eb73130c336ed5a627d0f7537902a81230f3881642ac7b654d150da32f8b7535f7506b346f6688f0917b9863d901d7a8746366667b53ede51802ff02e1#) " + "(y #2592db6bb37125ce400e66dad3c22eed0899cd47cff95589d577adcd106c805e4feaada954a103be18d41f657254bc2a182218a71e1eefcdfa8c4f69758068b416e4942d7ddd6398ec9b455cbbe7fb5cb943a04babc75bc3602bfef500014030cf5ee649939e690c6d5341bbc0155f14eb059d088e61070709b037f02cccf137#)" + ")" +")" + diff --git a/cyg-pubkey.h b/cyg-pubkey.h index 6d79544a..6952a9bb 100644 --- a/cyg-pubkey.h +++ b/cyg-pubkey.h @@ -1,14 +1,12 @@ /* Autogenerated from: ./cygwin.pub * by: ./gpg-key-to-s-expr.sh - * at: Mon Jun 16 02:20:11 2008 */ + * at: 27 Feb 2020 14:43:11 */ "(public-key " - "(dsa " - "(p #00b96e7de7db21b47aa365a60fc3ec39195d07c550164dd43e2c2ff36c5ca21242403716c8937a70d80cc142cb73498820dc8a1269acfdb1b3815cdb93047262788fd5fdddb095e03b47bf6daa3b55d61a4c6bfd74096716265311617b304e09977ec178abd22cc8b06821475f9e8ba8bebcbe26458cbcc293022b07fc5f4a91cb#) " - "(q #00d65d896bdd4fb133ba8ad55400260cbca3450871#) " - "(g #17a7506fa4611721f581ee0f01bd2f19218304846743481948192c73181d90e5716051a15eae3dca9ada22acf2fbc010d31c196aa8f9fb91c9c190ebece5167dbd2281eb73130c336ed5a627d0f7537902a81230f3881642ac7b654d150da32f8b7535f7506b346f6688f0917b9863d901d7a8746366667b53ede51802ff02e1#) " - "(y #2592db6bb37125ce400e66dad3c22eed0899cd47cff95589d577adcd106c805e4feaada954a103be18d41f657254bc2a182218a71e1eefcdfa8c4f69758068b416e4942d7ddd6398ec9b455cbbe7fb5cb943a04babc75bc3602bfef500014030cf5ee649939e690c6d5341bbc0155f14eb059d088e61070709b037f02cccf137#)" + "(rsa " + "(n #00d443c7f747c8f7d1801dd2cc7266fbe62876176d0c7cd0eabeb25a43b21dd12fe8b7e931074954d154a9742dcc8c485acbf5cc88049a553e5e9cdbffdfe87c158a4f91ed18b53cfcc7aad40c3de92bb8018fec48119c18f966cf08b26799d3db47e9f2e8228ce5bc883666d9155e76275d921ff9b15ad64d80426fc4313ce3f4d55d29c30b2acc2b6c43078050210781d4d941fcd31e605c4e650edce7cfeded783176bb44987b793dea69b4f2b51fac21fcc734b2dca846656ee41835dfc4be02c723b72a8ad6ac5df577c874b00d2e2de7ed9344aaf98337b8ef1360d980bd7fd89afbdc41cc881089f61b29e2d5366f089e3d474145fb843b8de17b6aa94908d685ebad85ecbd89ff0cb64ac45a9cc439baed950a1f89959f57d36db6729dd47face70497edce490244b3562c3706b0bf6da87ca8b9f95f50bdf029a6dc17055779b92cae4d4dcf6cf1ea1a824a53f1ce4da03f44cf5f0cfcbb0ca8d1240ea7daf5657eba08be045a2aaf3dd2380e4d589c99399031ca1458166066638eca6db38056c038298a41e373381c298dcf181f0e4428440ce7d7094efeaeb811b98fa3292e3bfc801c5ecf9dbf111abbdc020e5704d54f4fd9065cc376387c3ed1f1831e1cd3211bb239dee4eb206646578e20c9fd59951ac8e042b78f7004c07b8fb335ac5788e98019578b54810b253ab798038c73510a6c6fa5d9e9ec6ea953#) " + "(e #010001#) " ")" ")" diff --git a/cygwin-old.pub b/cygwin-old.pub new file mode 100644 index 00000000..8372e5fd Binary files /dev/null and b/cygwin-old.pub differ diff --git a/cygwin.pub b/cygwin.pub index 8372e5fd..42d1d4c3 100644 Binary files a/cygwin.pub and b/cygwin.pub differ diff --git a/gpg-error-config-fake b/gpg-error-config-fake deleted file mode 100755 index 7dcc92da..00000000 --- a/gpg-error-config-fake +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2008, Dave Korn. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# A copy of the GNU General Public License can be found at -# http://www.gnu.org/ -# -# Written by Dave Korn -# -# -# Fake gpg-error-config script redirects libgcrypt configure -# process to look for our newly-built libgpg-error. Crudely and -# viciously hacked up by ripping the guts out of the original -# until it does just enough to fool the libgcrypt configure -# process into using our freshly-built libgpg-error for us. -# - -output="" -mydir=`dirname $0` -while test $# -gt 0; do - case "$1" in - -*=*) - optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` - ;; - *) - optarg= - ;; - esac - - case $1 in - --version) - echo "1.6" - exit 0 - ;; - --dir=*) - mydir="${1#--dir=}" - ;; - --cflags) - output="$output -I$mydir/./libgpg-error/src/" - ;; - --libs) - output="$output -L$mydir/./libgpg-error/src/.libs" - output="$output -lgpg-error" - ;; - *) - echo "What's a $1?" - ;; - esac - shift -done - -echo $output diff --git a/gpg-key-to-s-expr.sh b/gpg-key-to-s-expr.sh index 1938bbfc..f5c6799a 100755 --- a/gpg-key-to-s-expr.sh +++ b/gpg-key-to-s-expr.sh @@ -13,7 +13,7 @@ # Written by Dave Korn # # -# Converts a gpg dsa pub key file to a text s-expr for +# Converts a gpg pub key file to a text s-expr for # building into setup.exe's signature verification. # Relies on having pgpdump installed. # @@ -28,11 +28,10 @@ # output on a single line. Only one option should be # specified or the behaviour is undefined. - -# Usage: find_a_line DSACOEFFICIENT PGPDUMPFILE -# Returns the hex data for the named DSA coefficeint.. +# Usage: find_a_line ALG COEFFICIENT PGPDUMPFILE +# Returns the hex data for the named coefficient.. function find_a_line() { - grep "DSA $1([0-9]* bits) -" < "$2" \ + grep -m1 "$1 $2([0-9]* bits) -" < "$3" \ | sed -e 's/^.*- //g' | tr -d ' ' } @@ -105,17 +104,36 @@ pgpdump -milpu "$1" >"$TMPFILE" || exit 1 # bunch of bashes, greps and seds. No, I don't care. Don't bug # me about it until we have to run this script a million times a day! -dsa_p=`find_a_line p $TMPFILE` -dsa_q=`find_a_line q $TMPFILE` -dsa_g=`find_a_line g $TMPFILE` -dsa_y=`find_a_line y $TMPFILE` +alg=`grep -m1 "Pub alg" $TMPFILE | sed -E -e 's/^.*pub (.*)\)/\1/g'` + +case $alg in + 1) + rsa_n=`find_a_line RSA n $TMPFILE` + rsa_e=`find_a_line RSA e $TMPFILE` + + rsa_n=`line_to_sexpr "$rsa_n"` + rsa_e=`line_to_sexpr "$rsa_e"` + + echo -e $header$quotes"(public-key $escapes$nl\ +$starts$ind$mid(rsa $escapes$nl\ +$starts$ind$ind$mid(n $rsa_n) $escapes$nl\ +$starts$ind$ind$mid(e $rsa_e) $escapes$nl\ +$starts$ind$mid)$escapes$nl\ +$starts$mid)$quotes$nl"; + ;; -dsa_p=`line_to_sexpr "$dsa_p"` -dsa_q=`line_to_sexpr "$dsa_q"` -dsa_g=`line_to_sexpr "$dsa_g"` -dsa_y=`line_to_sexpr "$dsa_y"` + 17) + dsa_p=`find_a_line DSA p $TMPFILE` + dsa_q=`find_a_line DSA q $TMPFILE` + dsa_g=`find_a_line DSA g $TMPFILE` + dsa_y=`find_a_line DSA y $TMPFILE` -echo -e $header$quotes"(public-key $escapes$nl\ + dsa_p=`line_to_sexpr "$dsa_p"` + dsa_q=`line_to_sexpr "$dsa_q"` + dsa_g=`line_to_sexpr "$dsa_g"` + dsa_y=`line_to_sexpr "$dsa_y"` + + echo -e $header$quotes"(public-key $escapes$nl\ $starts$ind$mid(dsa $escapes$nl\ $starts$ind$ind$mid(p $dsa_p) $escapes$nl\ $starts$ind$ind$mid(q $dsa_q) $escapes$nl\ @@ -124,4 +142,7 @@ $starts$ind$ind$mid(y $dsa_y)$escapes$nl\ $starts$ind$mid)$escapes$nl\ $starts$mid)$quotes$nl"; + ;; +esac + rm "$TMPFILE"