Reworked the ECC changes to better fit into the Libgcrypt API.
[gnupg.git] / g10 / misc.c
index dc2f73b..2052e96 100644 (file)
@@ -379,6 +379,19 @@ map_pk_openpgp_to_gcry (int algo)
     }
 }
 
+/* Map Gcrypt public key algorithm numbers to those used by
+   OpenPGP.  */
+int
+map_pk_gcry_to_openpgp (enum gcry_pk_algos algo)
+{
+  switch (algo)
+    {
+    case GCRY_PK_ECDSA:  return PUBKEY_ALGO_ECDSA;
+    case GCRY_PK_ECDH:   return PUBKEY_ALGO_ECDH;
+    default: return algo < 110 ? algo : 0;
+    }
+}
+
 
 /* Return the block length of an OpenPGP cipher algorithm.  */
 int 
@@ -1347,35 +1360,44 @@ path_access(const char *file,int mode)
 
 
 \f
-/* Temporary helper. */
+/* Return the number of public key parameters as used by OpenPGP.  */
 int
-pubkey_get_npkey( int algo )
+pubkey_get_npkey (int algo)
 {
   size_t n;
 
+  /* ECC is special.  */
+  if (algo == PUBKEY_ALGO_ECDSA)
+    return 2;
+  else if (algo == PUBKEY_ALGO_ECDH)
+    return 3;
+
+  /* All other algorithms match those of Libgcrypt.  */
   if (algo == GCRY_PK_ELG_E)
     algo = GCRY_PK_ELG;
-  else if (algo == PUBKEY_ALGO_ECDSA)
-    algo = GCRY_PK_ECDSA;
-  else if (algo == PUBKEY_ALGO_ECDH)
-    algo = GCRY_PK_ECDH;
-  if (gcry_pk_algo_info( algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &n))
+
+  if (gcry_pk_algo_info (algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &n))
     n = 0;
   return n;
 }
 
-/* Temporary helper. */
+
+/* Return the number of secret key parameters as used by OpenPGP.  */
 int
-pubkey_get_nskey( int algo )
+pubkey_get_nskey (int algo)
 {
   size_t n;
 
+  /* ECC is special.  */
+  if (algo == PUBKEY_ALGO_ECDSA)
+    return 3;
+  else if (algo == PUBKEY_ALGO_ECDH)
+    return 4;
+
+  /* All other algorithms match those of Libgcrypt.  */
   if (algo == GCRY_PK_ELG_E)
     algo = GCRY_PK_ELG;
-  else if (algo == PUBKEY_ALGO_ECDSA)
-    algo = GCRY_PK_ECDSA;
-  else if (algo == PUBKEY_ALGO_ECDH)
-    algo = GCRY_PK_ECDH;
+
   if (gcry_pk_algo_info( algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &n ))
     n = 0;
   return n;
@@ -1383,33 +1405,40 @@ pubkey_get_nskey( int algo )
 
 /* Temporary helper. */
 int
-pubkey_get_nsig( int algo )
+pubkey_get_nsig (int algo)
 {
   size_t n;
 
+  /* ECC is special.  */
+  if (algo == PUBKEY_ALGO_ECDSA)
+    return 2;
+  else if (algo == PUBKEY_ALGO_ECDH)
+    return 0;
+
   if (algo == GCRY_PK_ELG_E)
     algo = GCRY_PK_ELG;
-  else if (algo == PUBKEY_ALGO_ECDSA)
-    algo = GCRY_PK_ECDSA;
-  else if (algo == PUBKEY_ALGO_ECDH)
-    algo = GCRY_PK_ECDH;
+
   if (gcry_pk_algo_info( algo, GCRYCTL_GET_ALGO_NSIGN, NULL, &n))
     n = 0;
   return n;
 }
 
+
 /* Temporary helper. */
 int
-pubkey_get_nenc( int algo )
+pubkey_get_nenc (int algo)
 {
   size_t n;
   
+  /* ECC is special.  */
+  if (algo == PUBKEY_ALGO_ECDSA)
+    return 0;
+  else if (algo == PUBKEY_ALGO_ECDH)
+    return 2;
+
   if (algo == GCRY_PK_ELG_E)
     algo = GCRY_PK_ELG;
-  else if (algo == PUBKEY_ALGO_ECDSA)
-    algo = GCRY_PK_ECDSA;
-  else if (algo == PUBKEY_ALGO_ECDH)
-    algo = GCRY_PK_ECDH;
+
   if (gcry_pk_algo_info( algo, GCRYCTL_GET_ALGO_NENCR, NULL, &n ))
     n = 0;
   return n;
@@ -1442,9 +1471,16 @@ pubkey_nbits( int algo, gcry_mpi_t *key )
                                  key[0], key[1] );
     }
     else if( algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_ECDH ) {
-       rc = gcry_sexp_build ( &sexp, NULL,
-                             "(public-key(ecc(c%m)(q%m)))",
-                                 key[0], key[1] /* not affecting the size calculation, so use 'ecc' == 'ecdsa' */ );
+        char *curve = openpgp_oid_to_str (key[0]);
+        if (!curve)
+          rc = gpg_error_from_syserror ();
+        else
+          {
+            rc = gcry_sexp_build (&sexp, NULL,
+                                  "(public-key(ecc(curve%s)(q%m)))",
+                                 curve, key[1]);
+            xfree (curve);
+          }
     }
     else
        return 0;
@@ -1472,6 +1508,19 @@ mpi_print (estream_t fp, gcry_mpi_t a, int mode)
       n1 = gcry_mpi_get_nbits(a);
       n += es_fprintf (fp, "[%u bits]", n1);
     }
+  else if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
+    {
+      unsigned int nbits;
+      unsigned char *p = gcry_mpi_get_opaque (a, &nbits);
+      if (!p)
+        n += es_fprintf (fp, "[invalid opaque value]");
+      else
+        {
+          nbits = (nbits + 7)/8;
+          for (; nbits; nbits--, p++)
+            n += es_fprintf (fp, "%02X", *p);
+        }
+    }
   else
     {
       unsigned char *buffer;
@@ -1501,3 +1550,206 @@ ecdsa_qbits_from_Q (unsigned int qbits)
   qbits /= 2;
   return qbits;
 }
+
+
+
+/* Helper for openpgp_oid_from_str.  */
+static size_t
+make_flagged_int (unsigned long value, char *buf, size_t buflen)
+{
+  int more = 0;
+  int shift;
+
+  /* fixme: figure out the number of bits in an ulong and start with
+     that value as shift (after making it a multiple of 7) a more
+     straigtforward implementation is to do it in reverse order using
+     a temporary buffer - saves a lot of compares */
+  for (more=0, shift=28; shift > 0; shift -= 7)
+    {
+      if (more || value >= (1<<shift))
+        {
+          buf[buflen++] = 0x80 | (value >> shift);
+          value -= (value >> shift) << shift;
+          more = 1;
+        }
+    }
+  buf[buflen++] = value;
+  return buflen;
+}
+
+
+/* Convert the OID given in dotted decimal form in STRING to an DER
+ * encoding and store it as an opaque value at R_MPI.  The format of
+ * the DER encoded is not a regular ASN.1 object but the modified
+ * format as used by OpenPGP for the ECC curve description.  On error
+ * the function returns and error code an NULL is stored at R_BUG.
+ * Note that scanning STRING stops at the first white space
+ * character.  */
+gpg_error_t
+openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi)
+{
+  unsigned char *buf;
+  size_t buflen;
+  unsigned long val1, val;
+  const char *endp;
+  int arcno;
+
+  *r_mpi = NULL;
+
+  if (!string || !*string)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  /* We can safely assume that the encoded OID is shorter than the string. */
+  buf = xtrymalloc (1 + strlen (string) + 2);
+  if (!buf)
+    return gpg_error_from_syserror ();
+  /* Save the first byte for the length.  */
+  buflen = 1;
+
+  val1 = 0; /* Avoid compiler warning.  */
+  arcno = 0;
+  do {
+    arcno++;
+    val = strtoul (string, (char**)&endp, 10);
+    if (!digitp (string) || !(*endp == '.' || !*endp))
+      {
+        xfree (buf);
+        return gpg_error (GPG_ERR_INV_OID_STRING);
+      }
+    if (*endp == '.')
+      string = endp+1;
+
+    if (arcno == 1)
+      {
+        if (val > 2)
+          break; /* Not allowed, error catched below.  */
+        val1 = val;
+      }
+    else if (arcno == 2)
+      { /* Need to combine the first two arcs in one octet.  */
+        if (val1 < 2)
+          {
+            if (val > 39)
+              {
+                xfree (buf);
+                return gpg_error (GPG_ERR_INV_OID_STRING);
+              }
+            buf[buflen++] = val1*40 + val;
+          }
+        else
+          {
+            val += 80;
+            buflen = make_flagged_int (val, buf, buflen);
+          }
+      }
+    else
+      {
+        buflen = make_flagged_int (val, buf, buflen);
+      }
+  } while (*endp == '.');
+
+  if (arcno == 1 || buflen < 2 || buflen > 254 )
+    { /* It is not possible to encode only the first arc.  */
+      xfree (buf);
+      return gpg_error (GPG_ERR_INV_OID_STRING);
+    }
+
+  *buf = buflen - 1;
+  *r_mpi = gcry_mpi_set_opaque (NULL, buf, buflen * 8);
+  if (!*r_mpi)
+    {
+      xfree (buf);
+      return gpg_error_from_syserror ();
+    }
+  return 0; 
+}
+
+
+/* Return a malloced string represenation of the OID in the opaque MPI
+   A.  In case of an error NULL is returned and ERRNO is set.  */
+char *
+openpgp_oid_to_str (gcry_mpi_t a)
+{
+  const unsigned char *buf;
+  size_t length;
+  char *string, *p;
+  int n = 0;
+  unsigned long val, valmask;
+
+  valmask = (unsigned long)0xfe << (8 * (sizeof (valmask) - 1));
+
+  if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
+    {
+      gpg_err_set_errno (EINVAL);
+      return NULL;
+    }
+
+  buf = gcry_mpi_get_opaque (a, &length);
+  length = (length+7)/8;
+
+  /* The first bytes gives the length; check consistency.  */
+  if (!length || buf[0] != length -1)
+    {
+      gpg_err_set_errno (EINVAL);
+      return NULL;
+    }
+  /* Skip length byte.  */
+  length--;
+  buf++;
+
+  /* To calculate the length of the string we can safely assume an
+     upper limit of 3 decimal characters per byte.  Two extra bytes
+     account for the special first octect */
+  string = p = xtrymalloc (length*(1+3)+2+1);
+  if (!string)
+    return NULL;
+  if (!buf || !length)
+    {
+      *p = 0;
+      return string;
+    }
+
+  if (buf[0] < 40)
+    p += sprintf (p, "0.%d", buf[n]);
+  else if (buf[0] < 80)
+    p += sprintf (p, "1.%d", buf[n]-40);
+  else {
+    val = buf[n] & 0x7f;
+    while ( (buf[n]&0x80) && ++n < length )
+      {
+        if ( (val & valmask) )
+          goto badoid;  /* Overflow.  */
+        val <<= 7;
+        val |= buf[n] & 0x7f;
+      }
+    val -= 80;
+    sprintf (p, "2.%lu", val);
+    p += strlen (p);
+  }
+  for (n++; n < length; n++)
+    {
+      val = buf[n] & 0x7f;
+      while ( (buf[n]&0x80) && ++n < length )
+        {
+          if ( (val & valmask) )
+            goto badoid;  /* Overflow.  */
+          val <<= 7;
+          val |= buf[n] & 0x7f;
+        }
+      sprintf (p, ".%lu", val);
+      p += strlen (p);
+    }
+    
+  *p = 0;
+  return string;
+
+ badoid:
+  /* Return a special OID (gnu.gnupg.badoid) to indicate the error
+     case.  The OID is broken and thus we return one which can't do
+     any harm.  Formally this does not need to be a bad OID but an OID
+     with an arc that can't be represented in a 32 bit word is more
+     than likely corrupt.  */
+  xfree (string);
+  return xtrystrdup ("1.3.6.1.4.1.11591.2.12242973"); 
+}
+