diff options
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | doc/gcrypt.texi | 15 | ||||
-rw-r--r-- | mpi/mpicoder.c | 137 | ||||
-rw-r--r-- | tests/t-convert.c | 374 |
4 files changed, 495 insertions, 34 deletions
@@ -28,6 +28,9 @@ Noteworthy changes in version 1.6.0 (unreleased) * Added several MPI helper functions. + * Added support for negative numbers to gcry_mpi_print, + gcry_mpi_aprint and gcry_mpi_scan. + * Interface changes relative to the 1.5.0 release: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gcry_ac_* REMOVED. diff --git a/doc/gcrypt.texi b/doc/gcrypt.texi index afef7f76..63fd5963 100644 --- a/doc/gcrypt.texi +++ b/doc/gcrypt.texi @@ -3826,7 +3826,8 @@ bytes actually scanned unless @var{nscanned} was given as @table @code @item GCRYMPI_FMT_STD -2-complement stored without a length header. +2-complement stored without a length header. Note that +@code{gcry_mpi_print} stores a @code{0} as a string of zero length. @item GCRYMPI_FMT_PGP As used by OpenPGP (only defined as unsigned). This is basically @@ -3837,8 +3838,10 @@ As used in the Secure Shell protocol. This is @code{GCRYMPI_FMT_STD} with a 4 byte big endian header. @item GCRYMPI_FMT_HEX -Stored as a C style string with each byte of the MPI encoded as 2 hex -digits. When using this format, @var{buflen} must be zero. +Stored as a string with each byte of the MPI encoded as 2 hex digits. +Negative numbers are prefix with a minus sign and in addition the +high bit is always zero to make clear that an explicit sign ist used. +When using this format, @var{buflen} must be zero. @item GCRYMPI_FMT_USG Simple unsigned integer. @@ -3866,6 +3869,12 @@ Convert the MPI @var{a} into an external representation described by address will be stored in the variable @var{buffer} points to. The number of bytes stored in this buffer will be stored in the variable @var{nbytes} points to, unless @var{nbytes} is @code{NULL}. + +Even if @var{nbytes} is zero, the function allocates at least one byte +and store a zero there. Thus with formats @code{GCRYMPI_FMT_STD} and +@code{GCRYMPI_FMT_USG} the caller may safely set a returned length of +0 to 1 to represent a zero as a 1 byte string. + @end deftypefun @deftypefun void gcry_mpi_dump (@w{const gcry_mpi_t @var{a}}) diff --git a/mpi/mpicoder.c b/mpi/mpicoder.c index 19c8de90..07e91c63 100644 --- a/mpi/mpicoder.c +++ b/mpi/mpicoder.c @@ -353,6 +353,65 @@ _gcry_mpi_set_buffer (gcry_mpi_t a, const void *buffer_arg, } +static void +onecompl (gcry_mpi_t a) +{ + mpi_ptr_t ap; + mpi_size_t n; + unsigned int i; + unsigned int nbits = mpi_get_nbits (a); + + if (mpi_is_immutable (a)) + { + mpi_immutable_failed (); + return; + } + + mpi_normalize (a); + ap = a->d; + n = a->nlimbs; + + for( i = 0; i < n; i++ ) + ap[i] ^= (mpi_limb_t)(-1); + + a->sign = 0; + mpi_clear_highbit (a, nbits-1); +} + + +/* Perform a two's complement operation on buffer P of size N bytes. */ +static void +twocompl (unsigned char *p, unsigned int n) +{ + int i; + + for (i=n-1; i >= 0 && !p[i]; i--) + ; + if (i >= 0) + { + if ((p[i] & 0x01)) + p[i] = (((p[i] ^ 0xfe) | 0x01) & 0xff); + else if ((p[i] & 0x02)) + p[i] = (((p[i] ^ 0xfc) | 0x02) & 0xfe); + else if ((p[i] & 0x04)) + p[i] = (((p[i] ^ 0xf8) | 0x04) & 0xfc); + else if ((p[i] & 0x08)) + p[i] = (((p[i] ^ 0xf0) | 0x08) & 0xf8); + else if ((p[i] & 0x10)) + p[i] = (((p[i] ^ 0xe0) | 0x10) & 0xf0); + else if ((p[i] & 0x20)) + p[i] = (((p[i] ^ 0xc0) | 0x20) & 0xe0); + else if ((p[i] & 0x40)) + p[i] = (((p[i] ^ 0x80) | 0x40) & 0xc0); + else + p[i] = 0x80; + + for (i--; i >= 0; i--) + p[i] ^= 0xff; + } +} + + /* Convert the external representation of an integer stored in BUFFER with a length of BUFLEN into a newly create MPI returned in RET_MPI. If NBYTES is not NULL, it will receive the number of @@ -380,15 +439,14 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format, : mpi_alloc ((len+BYTES_PER_MPI_LIMB-1)/BYTES_PER_MPI_LIMB); if (len) { + _gcry_mpi_set_buffer (a, s, len, 0); a->sign = !!(*s & 0x80); if (a->sign) { - /* FIXME: we have to convert from 2compl to magnitude format */ - mpi_free (a); - return gcry_error (GPG_ERR_INTERNAL); + onecompl (a); + mpi_add_ui (a, a, 1); + a->sign = 1; } - else - _gcry_mpi_set_buffer (a, s, len, 0); } if (ret_mpi) { @@ -397,6 +455,8 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format, } else mpi_free(a); + if (nscanned) + *nscanned = len; return 0; } else if (format == GCRYMPI_FMT_USG) @@ -414,6 +474,8 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format, } else mpi_free(a); + if (nscanned) + *nscanned = len; return 0; } else if (format == GCRYMPI_FMT_PGP) @@ -458,14 +520,13 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format, if (n) { a->sign = !!(*s & 0x80); + _gcry_mpi_set_buffer( a, s, n, 0 ); if (a->sign) { - /* FIXME: we have to convert from 2compl to magnitude format */ - mpi_free(a); - return gcry_error (GPG_ERR_INTERNAL); + onecompl (a); + mpi_add_ui (a, a, 1); + a->sign = 1; } - else - _gcry_mpi_set_buffer( a, s, n, 0 ); } if (nscanned) *nscanned = n+4; @@ -497,6 +558,8 @@ gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format, } else mpi_free(a); + if (nscanned) + *nscanned = strlen ((const char*)buffer); return 0; } else @@ -541,18 +604,25 @@ gcry_mpi_print (enum gcry_mpi_format format, int extra = 0; unsigned int n; - if (negative) - return gcry_error (GPG_ERR_INTERNAL); /* Can't handle it yet. */ - tmp = _gcry_mpi_get_buffer (a, 0, &n, NULL); if (!tmp) return gpg_error_from_syserror (); - /* If the high bit of the returned buffer is set, we need to - print an extra leading 0x00 so that the output is interpreted - as a positive number. */ - if (n && (*tmp & 0x80)) + if (negative) { + twocompl (tmp, n); + if (!(*tmp & 0x80)) + { + /* Need to extend the sign. */ + n++; + extra = 2; + } + } + else if (n && (*tmp & 0x80)) + { + /* Positive but the high bit of the returned buffer is set. + Thus we need to print an extra leading 0x00 so that the + output is interpreted as a positive number. */ n++; extra = 1; } @@ -567,9 +637,11 @@ gcry_mpi_print (enum gcry_mpi_format format, { unsigned char *s = buffer; - if (extra) + if (extra == 1) *s++ = 0; - memcpy (s, tmp, n-extra); + else if (extra) + *s++ = 0xff; + memcpy (s, tmp, n-!!extra); } gcry_free(tmp); *nwritten = n; @@ -632,13 +704,21 @@ gcry_mpi_print (enum gcry_mpi_format format, int extra = 0; unsigned int n; - if (negative) - return gcry_error (GPG_ERR_INTERNAL); /* Can't handle it yet. */ - tmp = _gcry_mpi_get_buffer (a, 0, &n, NULL); if (!tmp) return gpg_error_from_syserror (); - if (n && (*tmp & 0x80)) + + if (negative) + { + twocompl (tmp, n); + if (!(*tmp & 0x80)) + { + /* Need to extend the sign. */ + n++; + extra = 2; + } + } + else if (n && (*tmp & 0x80)) { n++; extra=1; @@ -658,10 +738,11 @@ gcry_mpi_print (enum gcry_mpi_format format, *s++ = n >> 16; *s++ = n >> 8; *s++ = n; - if (extra) + if (extra == 1) *s++ = 0; - - memcpy (s, tmp, n-extra); + else if (extra) + *s++ = 0xff; + memcpy (s, tmp, n-!!extra); } gcry_free (tmp); *nwritten = 4+n; @@ -741,6 +822,10 @@ gcry_mpi_aprint (enum gcry_mpi_format format, *buffer = mpi_is_secure(a) ? gcry_malloc_secure (n?n:1) : gcry_malloc (n?n:1); if (!*buffer) return gpg_error_from_syserror (); + /* If the returned buffer will have a length of 0, we nevertheless + allocated 1 byte (malloc needs it anyway) and store a 0. */ + if (!n) + **buffer = 0; rc = gcry_mpi_print( format, *buffer, n, &n, a ); if (rc) { diff --git a/tests/t-convert.c b/tests/t-convert.c index b7f97fa3..d44c4395 100644 --- a/tests/t-convert.c +++ b/tests/t-convert.c @@ -28,7 +28,10 @@ #include "../src/gcrypt-int.h" -#define PGM "t-mpi-point" +#define PGM "t-convert" + +#define DIM(v) (sizeof(v)/sizeof((v)[0])) +#define DIMof(type,member) DIM(((type *)0)->member) static const char *wherestr; static int verbose; @@ -55,6 +58,61 @@ show (const char *format, ...) } static void +showhex (const char *prefix, const void *buffer, size_t buflen) +{ + const unsigned char *s; + + if (!verbose) + return; + fprintf (stderr, "%s: %s ", PGM, prefix); + for (s= buffer; buflen; buflen--, s++) + fprintf (stderr, "%02x", *s); + putc ('\n', stderr); +} + + +/* Allocate a bit string consisting of '0' and '1' from the MPI A. Do + not return any leading zero bits. Caller needs to gcry_free the + result. */ +static char * +mpi2bitstr_nlz (gcry_mpi_t a) +{ + char *p, *buf; + size_t length = gcry_mpi_get_nbits (a); + + if (!length) + { + buf = p = xmalloc (3); + *p++ = ' '; + *p++ = '0'; + } + else + { + buf = p = xmalloc (length + 1 + 1); + *p++ = gcry_mpi_is_neg (a)? '-':' '; + while (length-- > 1) + *p++ = gcry_mpi_test_bit (a, length) ? '1':'0'; + *p++ = gcry_mpi_test_bit (a, 0) ? '1':'0'; + } + *p = 0; + return buf; +} + + +static void +showmpi (const char *prefix, gcry_mpi_t a) +{ + char *bitstr; + + if (!verbose) + return; + bitstr = mpi2bitstr_nlz (a); + fprintf (stderr, "%s: %s%s\n", PGM, prefix, bitstr); + xfree (bitstr); +} + + +static void fail (const char *format, ...) { va_list arg_ptr; @@ -95,10 +153,10 @@ negative_zero (void) void *bufaddr = &buf; struct { const char *name; enum gcry_mpi_format format; } fmts[] = { - { "STD", GCRYMPI_FMT_STD }, - { "PGP", GCRYMPI_FMT_PGP }, - { "SSH", GCRYMPI_FMT_SSH }, - { "HEX", GCRYMPI_FMT_HEX }, + /* { "STD", GCRYMPI_FMT_STD }, */ + /* { "PGP", GCRYMPI_FMT_PGP }, */ + /* { "SSH", GCRYMPI_FMT_SSH }, */ + /* { "HEX", GCRYMPI_FMT_HEX }, */ { "USG", GCRYMPI_FMT_USG }, { NULL, 0 } }; @@ -134,6 +192,311 @@ negative_zero (void) } +static void +check_formats (void) +{ + static struct { + int value; + struct { + const char *hex; + size_t stdlen; + const char *std; + size_t sshlen; + const char *ssh; + size_t usglen; + const char *usg; + } a; + } data[] = { + { 0, { "00", + 0, "", + 4, "\x00\x00\x00\x00", + 0, "" } + }, + { 1, { "01", + 1, "\x01", + 5, "\x00\x00\x00\x01\x01", + 1, "\x01" } + }, + { 2, { "02", + 1, "\x02", + 5, "\x00\x00\x00\x01\x02", + 1, "\x02", } + }, + { 127, { "7F", + 1, "\x7f", + 5, "\x00\x00\x00\x01\x7f", + 1, "\x7f" } + }, + { 128, { "0080", + 2, "\x00\x80", + 6, "\x00\x00\x00\x02\x00\x80", + 1, "\x80" } + }, + { 129, { "0081", + 2, "\x00\x81", + 6, "\x00\x00\x00\x02\x00\x81", + 1, "\x81" } + }, + { 255, { "00FF", + 2, "\x00\xff", + 6, "\x00\x00\x00\x02\x00\xff", + 1, "\xff" } + }, + { 256, { "0100", + 2, "\x01\x00", + 6, "\x00\x00\x00\x02\x01\x00", + 2, "\x01\x00" } + }, + { 257, { "0101", + 2, "\x01\x01", + 6, "\x00\x00\x00\x02\x01\x01", + 2, "\x01\x01" } + }, + { -1, { "-01", + 1, "\xff", + 5, "\x00\x00\x00\x01\xff", + 1,"\x01" } + }, + { -2, { "-02", + 1, "\xfe", + 5, "\x00\x00\x00\x01\xfe", + 1, "\x02" } + }, + { -127, { "-7F", + 1, "\x81", + 5, "\x00\x00\x00\x01\x81", + 1, "\x7f" } + }, + { -128, { "-0080", + 1, "\x80", + 5, "\x00\x00\x00\x01\x80", + 1, "\x80" } + }, + { -129, { "-0081", + 2, "\xff\x7f", + 6, "\x00\x00\x00\x02\xff\x7f", + 1, "\x81" } + }, + { -255, { "-00FF", + 2, "\xff\x01", + 6, "\x00\x00\x00\x02\xff\x01", + 1, "\xff" } + }, + { -256, { "-0100", + 2, "\xff\x00", + 6, "\x00\x00\x00\x02\xff\x00", + 2, "\x01\x00" } + }, + { -257, { "-0101", + 2, "\xfe\xff", + 6, "\x00\x00\x00\x02\xfe\xff", + 2, "\x01\x01" } + }, + { 65535, { "00FFFF", + 3, "\x00\xff\xff", + 7, "\x00\x00\x00\x03\x00\xff\xff", + 2, "\xff\xff" } + }, + { 65536, { "010000", + 3, "\x01\00\x00", + 7, "\x00\x00\x00\x03\x01\x00\x00", + 3, "\x01\x00\x00" } + }, + { 65537, { "010001", + 3, "\x01\00\x01", + 7, "\x00\x00\x00\x03\x01\x00\x01", + 3, "\x01\x00\x01" } + }, + { -65537, { "-010001", + 3, "\xfe\xff\xff", + 7, "\x00\x00\x00\x03\xfe\xff\xff", + 3, "\x01\x00\x01" } + }, + { -65536, { "-010000", + 3, "\xff\x00\x00", + 7, "\x00\x00\x00\x03\xff\x00\x00", + 3, "\x01\x00\x00" } + }, + { -65535, { "-00FFFF", + 3, "\xff\x00\x01", + 7, "\x00\x00\x00\x03\xff\x00\x01", + 2, "\xff\xff" } + } + }; + gpg_error_t err; + gcry_mpi_t a, b; + char *buf; + void *bufaddr = &buf; + int idx; + size_t buflen; + + a = gcry_mpi_new (0); + for (idx=0; idx < DIM(data); idx++) + { + if (debug) + show ("print test %d\n", data[idx].value); + + if (data[idx].value < 0) + { + gcry_mpi_set_ui (a, -data[idx].value); + gcry_mpi_neg (a, a); + } + else + gcry_mpi_set_ui (a, data[idx].value); + + err = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); + if (err) + fail ("error printing value %d as %s: %s\n", + data[idx].value, "HEX", gpg_strerror (err)); + else + { + if (strcmp (buf, data[idx].a.hex)) + { + fail ("error printing value %d as %s: %s\n", + data[idx].value, "HEX", "wrong result"); + show ("expected: '%s'\n", data[idx].a.hex); + show (" got: '%s'\n", buf); + } + gcry_free (buf); + } + + err = gcry_mpi_aprint (GCRYMPI_FMT_STD, bufaddr, &buflen, a); + if (err) + fail ("error printing value %d as %s: %s\n", + data[idx].value, "STD", gpg_strerror (err)); + else + { + if (buflen != data[idx].a.stdlen + || memcmp (buf, data[idx].a.std, data[idx].a.stdlen)) + { + fail ("error printing value %d as %s: %s\n", + data[idx].value, "STD", "wrong result"); + showhex ("expected:", data[idx].a.std, data[idx].a.stdlen); + showhex (" got:", buf, buflen); + } + gcry_free (buf); + } + + err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, bufaddr, &buflen, a); + if (err) + fail ("error printing value %d as %s: %s\n", + data[idx].value, "SSH", gpg_strerror (err)); + else + { + if (buflen != data[idx].a.sshlen + || memcmp (buf, data[idx].a.ssh, data[idx].a.sshlen)) + { + fail ("error printing value %d as %s: %s\n", + data[idx].value, "SSH", "wrong result"); + showhex ("expected:", data[idx].a.ssh, data[idx].a.sshlen); + showhex (" got:", buf, buflen); + } + gcry_free (buf); + } + + err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufaddr, &buflen, a); + if (err) + fail ("error printing value %d as %s: %s\n", + data[idx].value, "USG", gpg_strerror (err)); + else + { + if (buflen != data[idx].a.usglen + || memcmp (buf, data[idx].a.usg, data[idx].a.usglen)) + { + fail ("error printing value %d as %s: %s\n", + data[idx].value, "USG", "wrong result"); + showhex ("expected:", data[idx].a.usg, data[idx].a.usglen); + showhex (" got:", buf, buflen); + } + gcry_free (buf); + } + } + + + /* Now for the other direction. */ + for (idx=0; idx < DIM(data); idx++) + { + if (debug) + show ("scan test %d\n", data[idx].value); + + if (data[idx].value < 0) + { + gcry_mpi_set_ui (a, -data[idx].value); + gcry_mpi_neg (a, a); + } + else + gcry_mpi_set_ui (a, data[idx].value); + + err = gcry_mpi_scan (&b, GCRYMPI_FMT_HEX, data[idx].a.hex, 0, &buflen); + if (err) + fail ("error scanning value %d from %s: %s\n", + data[idx].value, "HEX", gpg_strerror (err)); + else + { + if (gcry_mpi_cmp (a, b)) + { + fail ("error scanning value %d from %s: %s\n", + data[idx].value, "HEX", "wrong result"); + showmpi ("expected:", a); + showmpi (" got:", b); + } + gcry_mpi_release (b); + } + + err = gcry_mpi_scan (&b, GCRYMPI_FMT_STD, + data[idx].a.std, data[idx].a.stdlen, &buflen); + if (err) + fail ("error scanning value %d as %s: %s\n", + data[idx].value, "STD", gpg_strerror (err)); + else + { + if (gcry_mpi_cmp (a, b) || data[idx].a.stdlen != buflen) + { + fail ("error scanning value %d from %s: %s (%u)\n", + data[idx].value, "STD", "wrong result", buflen); + showmpi ("expected:", a); + showmpi (" got:", b); + } + gcry_mpi_release (b); + } + + err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, bufaddr, &buflen, a); + if (err) + fail ("error printing value %d as %s: %s\n", + data[idx].value, "SSH", gpg_strerror (err)); + else + { + if (buflen != data[idx].a.sshlen + || memcmp (buf, data[idx].a.ssh, data[idx].a.sshlen)) + { + fail ("error printing value %d as %s: %s\n", + data[idx].value, "SSH", "wrong result"); + showhex ("expected:", data[idx].a.ssh, data[idx].a.sshlen); + showhex (" got:", buf, buflen); + } + gcry_free (buf); + } + + err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufaddr, &buflen, a); + if (err) + fail ("error printing value %d as %s: %s\n", + data[idx].value, "USG", gpg_strerror (err)); + else + { + if (buflen != data[idx].a.usglen + || memcmp (buf, data[idx].a.usg, data[idx].a.usglen)) + { + fail ("error printing value %d as %s: %s\n", + data[idx].value, "USG", "wrong result"); + showhex ("expected:", data[idx].a.usg, data[idx].a.usglen); + showhex (" got:", buf, buflen); + } + gcry_free (buf); + } + } + + gcry_mpi_release (a); +} int @@ -154,6 +517,7 @@ main (int argc, char **argv) gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); negative_zero (); + check_formats (); show ("All tests completed. Errors: %d\n", error_count); return error_count ? 1 : 0; |