LCOV - code coverage report
Current view: top level - pkcs11/ssh-store - gkm-ssh-openssh.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 82.5 % 211 174
Test Date: 2024-09-14 11:48:09 Functions: 100.0 % 14 14

            Line data    Source code
       1              : 
       2              : #include "gkm-ssh-openssh.h"
       3              : 
       4              : #include "gkm/gkm-data-asn1.h"
       5              : #include "gkm/gkm-data-der.h"
       6              : #include "gkm/gkm-data-types.h"
       7              : 
       8              : #include "egg/egg-armor.h"
       9              : #include "egg/egg-asn1x.h"
      10              : #include "egg/egg-buffer.h"
      11              : #include "egg/egg-openssl.h"
      12              : #include "egg/egg-secure-memory.h"
      13              : 
      14              : typedef struct _ParsePrivate {
      15              :         gcry_sexp_t sexp;
      16              :         gboolean seen;
      17              :         GkmDataResult result;
      18              :         const gchar *password;
      19              :         gssize n_password;
      20              : } ParsePrivate;
      21              : 
      22              : /* ------------------------------------------------------------------------------
      23              :  * INTERNAL
      24              :  */
      25              : 
      26              : static int
      27           28 : keytype_to_algo (const gchar *salgo)
      28              : {
      29           28 :         g_return_val_if_fail (salgo, 0);
      30           28 :         if (strcmp (salgo, "ssh-rsa") == 0)
      31            8 :                 return GCRY_PK_RSA;
      32           20 :         else if (strcmp (salgo, "ssh-dss") == 0)
      33            8 :                 return GCRY_PK_DSA;
      34           12 :         else if ((strcmp (salgo, "ecdsa-sha2-nistp256") == 0)
      35            4 :             || (strcmp (salgo, "ecdsa-sha2-nistp384") == 0)
      36            2 :             || (strcmp (salgo, "ecdsa-sha2-nistp521") == 0))
      37           12 :                 return GCRY_PK_ECC;
      38            0 :         return 0;
      39              : }
      40              : 
      41              : static const gchar *
      42            6 : curve_to_gcry (const gchar *salgo)
      43              : {
      44            6 :         g_return_val_if_fail (salgo, 0);
      45            6 :         if (strcmp (salgo, "nistp256") == 0)
      46            4 :                 return "NIST P-256";
      47            2 :         else if (strcmp (salgo, "nistp384") == 0)
      48            1 :                 return "NIST P-384";
      49            1 :         else if (strcmp (salgo, "nistp521") == 0)
      50            1 :                 return "NIST P-521";
      51            0 :         return NULL;
      52              : }
      53              : 
      54              : static gboolean
      55           24 : read_mpi (EggBuffer *req, gsize *offset, gcry_mpi_t *mpi)
      56              : {
      57              :         const guchar *data;
      58              :         gsize len;
      59              :         gcry_error_t gcry;
      60              : 
      61           24 :         if (!egg_buffer_get_byte_array (req, *offset, offset, &data, &len))
      62            0 :                 return FALSE;
      63              : 
      64           24 :         gcry = gcry_mpi_scan (mpi, GCRYMPI_FMT_USG, data, len, NULL);
      65           24 :         if (gcry)
      66            0 :                 return FALSE;
      67              : 
      68           24 :         return TRUE;
      69              : }
      70              : 
      71              : #define SEXP_PUBLIC_DSA  \
      72              :         "(public-key"    \
      73              :         "  (dsa"         \
      74              :         "    (p %m)"     \
      75              :         "    (q %m)"     \
      76              :         "    (g %m)"     \
      77              :         "    (y %m)))"
      78              : 
      79              : static gboolean
      80            4 : read_public_dsa (EggBuffer *req, gsize *offset, gcry_sexp_t *sexp)
      81              : {
      82              :         gcry_mpi_t p, q, g, y;
      83              :         int gcry;
      84              : 
      85            8 :         if (!read_mpi (req, offset, &p) ||
      86            8 :             !read_mpi (req, offset, &q) ||
      87            8 :             !read_mpi (req, offset, &g) ||
      88            4 :             !read_mpi (req, offset, &y))
      89            0 :                 return FALSE;
      90              : 
      91            4 :         gcry = gcry_sexp_build (sexp, NULL, SEXP_PUBLIC_DSA, p, q, g, y);
      92            4 :         if (gcry) {
      93            0 :                 g_warning ("couldn't parse incoming public DSA key: %s", gcry_strerror (gcry));
      94            0 :                 return FALSE;
      95              :         }
      96              : 
      97            4 :         gcry_mpi_release (p);
      98            4 :         gcry_mpi_release (q);
      99            4 :         gcry_mpi_release (g);
     100            4 :         gcry_mpi_release (y);
     101              : 
     102            4 :         return TRUE;
     103              : }
     104              : 
     105              : #define SEXP_PUBLIC_ECDSA  \
     106              :         "(public-key"      \
     107              :         "  (ecdsa"         \
     108              :         "    (curve %s)"   \
     109              :         "    (q %b)))"
     110              : 
     111              : static gboolean
     112            6 : read_public_ecdsa (EggBuffer *req, gsize *offset, gcry_sexp_t *sexp)
     113              : {
     114              :         int gcry;
     115            6 :         const gchar *curve_name = NULL;
     116            6 :         const guchar *q = NULL;
     117              :         size_t q_len;
     118            6 :         gchar *curve = NULL;
     119              : 
     120            6 :         if (!egg_buffer_get_string (req, *offset, offset, &curve, (EggBufferAllocator)g_realloc))
     121            0 :                 return FALSE;
     122              : 
     123            6 :         if (!egg_buffer_get_byte_array (req, *offset, offset, &q, &q_len))
     124            0 :                 return FALSE;
     125              : 
     126            6 :         curve_name = curve_to_gcry (curve);
     127            6 :         g_return_val_if_fail (curve_name, FALSE);
     128              : 
     129            6 :         gcry = gcry_sexp_build (sexp, NULL, SEXP_PUBLIC_ECDSA,
     130              :                                 curve_name, q_len, q);
     131            6 :         if (gcry) {
     132            0 :                 g_warning ("couldn't parse incoming public ECDSA key: %s", gcry_strerror (gcry));
     133            0 :                 return FALSE;
     134              :         }
     135              : 
     136            6 :         return TRUE;
     137              : }
     138              : 
     139              : #define SEXP_PUBLIC_RSA  \
     140              :         "(public-key"    \
     141              :         "  (rsa"         \
     142              :         "    (n %m)"     \
     143              :         "    (e %m)))"
     144              : 
     145              : static gboolean
     146            4 : read_public_rsa (EggBuffer *req, gsize *offset, gcry_sexp_t *sexp)
     147              : {
     148              :         gcry_mpi_t n, e;
     149              :         int gcry;
     150              : 
     151            8 :         if (!read_mpi (req, offset, &e) ||
     152            4 :             !read_mpi (req, offset, &n))
     153            0 :                 return FALSE;
     154              : 
     155            4 :         gcry = gcry_sexp_build (sexp, NULL, SEXP_PUBLIC_RSA, n, e);
     156            4 :         if (gcry) {
     157            0 :                 g_warning ("couldn't parse incoming public RSA key: %s", gcry_strerror (gcry));
     158            0 :                 return FALSE;
     159              :         }
     160              : 
     161            4 :         gcry_mpi_release (n);
     162            4 :         gcry_mpi_release (e);
     163              : 
     164            4 :         return TRUE;
     165              : }
     166              : 
     167              : static gboolean
     168           14 : read_public (EggBuffer *req, gsize *offset, gcry_sexp_t *key, int *algo)
     169              : {
     170              :         gboolean ret;
     171              :         gchar *stype;
     172              :         int alg;
     173              : 
     174              :         /* The string algorithm */
     175           14 :         if (!egg_buffer_get_string (req, *offset, offset, &stype, (EggBufferAllocator)g_realloc))
     176            0 :                 return FALSE;
     177              : 
     178           14 :         alg = keytype_to_algo (stype);
     179           14 :         g_free (stype);
     180              : 
     181           14 :         if (!alg) {
     182            0 :                 g_warning ("unsupported algorithm from SSH: %s", stype);
     183            0 :                 return FALSE;
     184              :         }
     185              : 
     186           14 :         switch (alg) {
     187            4 :         case GCRY_PK_RSA:
     188            4 :                 ret = read_public_rsa (req, offset, key);
     189            4 :                 break;
     190            4 :         case GCRY_PK_DSA:
     191            4 :                 ret = read_public_dsa (req, offset, key);
     192            4 :                 break;
     193            6 :         case GCRY_PK_ECC:
     194            6 :                 ret = read_public_ecdsa (req, offset, key);
     195            6 :                 break;
     196            0 :         default:
     197            0 :                 g_assert_not_reached ();
     198              :                 return FALSE;
     199              :         }
     200              : 
     201           14 :         if (!ret) {
     202            0 :                 g_warning ("couldn't read incoming SSH private key");
     203            0 :                 return FALSE;
     204              :         }
     205              : 
     206           14 :         if (algo)
     207            0 :                 *algo = alg;
     208           14 :         return TRUE;
     209              : }
     210              : 
     211              : static GkmDataResult
     212           12 : load_encrypted_key (GBytes *data,
     213              :                     const gchar *dekinfo,
     214              :                     const gchar *password,
     215              :                     gssize n_password,
     216              :                     gcry_sexp_t *skey)
     217              : {
     218           12 :         guchar *decrypted = NULL;
     219           12 :         gsize n_decrypted = 0;
     220              :         GBytes *bytes;
     221              :         GkmDataResult ret;
     222              :         gint length;
     223              : 
     224              :         /* Decrypt, this will result in garble if invalid password */
     225           12 :         decrypted = egg_openssl_decrypt_block (dekinfo, password, n_password,
     226              :                                                data, &n_decrypted);
     227           12 :         if (!decrypted)
     228            0 :                 return FALSE;
     229              : 
     230              :         /* Unpad the DER data */
     231           12 :         length = egg_asn1x_element_length (decrypted, n_decrypted);
     232           12 :         if (length > 0)
     233           10 :                 n_decrypted = length;
     234              : 
     235           12 :         bytes = g_bytes_new_with_free_func (decrypted, n_decrypted, egg_secure_free, decrypted);
     236              : 
     237              :         /* Try to parse */
     238           12 :         ret = gkm_data_der_read_private_key (bytes, skey);
     239           12 :         g_bytes_unref (bytes);
     240              : 
     241           12 :         if (ret != GKM_DATA_UNRECOGNIZED)
     242            6 :                 return ret;
     243              : 
     244            6 :         return GKM_DATA_LOCKED;
     245              : }
     246              : 
     247              : static gboolean
     248           22 : is_private_key_type (GQuark type)
     249              : {
     250              :         static GQuark PEM_RSA_PRIVATE_KEY;
     251              :         static GQuark PEM_DSA_PRIVATE_KEY;
     252              :         static GQuark PEM_ECDSA_PRIVATE_KEY;
     253              :         static gsize quarks_inited = 0;
     254              : 
     255              :         /* Initialize the first time through */
     256           22 :         if (g_once_init_enter (&quarks_inited)) {
     257            2 :                 PEM_RSA_PRIVATE_KEY = g_quark_from_static_string ("RSA PRIVATE KEY");
     258            2 :                 PEM_DSA_PRIVATE_KEY = g_quark_from_static_string ("DSA PRIVATE KEY");
     259            2 :                 PEM_ECDSA_PRIVATE_KEY = g_quark_from_static_string ("EC PRIVATE KEY");
     260            2 :                 g_once_init_leave (&quarks_inited, 1);
     261              :         }
     262              : 
     263              :         /* Only handle SSHv2 private keys */
     264           38 :         return (type == PEM_RSA_PRIVATE_KEY ||
     265           30 :                 type == PEM_DSA_PRIVATE_KEY ||
     266            8 :                 type == PEM_ECDSA_PRIVATE_KEY);
     267              : }
     268              : 
     269              : static void
     270           20 : parsed_pem_block (GQuark type,
     271              :                   GBytes *data,
     272              :                   GBytes *outer,
     273              :                   GHashTable *headers,
     274              :                   gpointer user_data)
     275              : {
     276           20 :         ParsePrivate *ctx = (ParsePrivate*)user_data;
     277              :         const gchar *dekinfo;
     278              : 
     279           20 :         if (!is_private_key_type (type))
     280            0 :                 return;
     281              : 
     282           20 :         ctx->seen = TRUE;
     283              : 
     284              :         /* Only parse first key in the file */
     285           20 :         if (ctx->sexp)
     286            0 :                 return;
     287              : 
     288              :         /* If it's encrypted ... */
     289           20 :         dekinfo = egg_openssl_get_dekinfo (headers);
     290           20 :         if (dekinfo) {
     291           12 :                 ctx->result = load_encrypted_key (data, dekinfo, ctx->password,
     292              :                                                   ctx->n_password, &ctx->sexp);
     293              : 
     294              :         /* not encryted, just load the data */
     295              :         } else {
     296            8 :                 ctx->result = gkm_data_der_read_private_key (data, &ctx->sexp);
     297              :         }
     298              : }
     299              : 
     300              : static void
     301            2 : digest_pem_block (GQuark type,
     302              :                   GBytes *data,
     303              :                   GBytes *outer,
     304              :                   GHashTable *headers,
     305              :                   gpointer user_data)
     306              : {
     307            2 :         gchar **result = (gchar**)user_data;
     308              : 
     309            2 :         g_assert (result);
     310              : 
     311            2 :         if (!is_private_key_type (type))
     312            0 :                 return;
     313              : 
     314              :         /* Only digest the first key in the file */
     315            2 :         if (*result != NULL)
     316            0 :                 return;
     317              : 
     318            4 :         *result = g_compute_checksum_for_data (G_CHECKSUM_SHA1,
     319            2 :                                                g_bytes_get_data (data, NULL),
     320              :                                                g_bytes_get_size (data));
     321              : }
     322              : 
     323              : /* ------------------------------------------------------------------------------
     324              :  * PUBLIC
     325              :  */
     326              : 
     327              : GkmDataResult
     328           14 : gkm_ssh_openssh_parse_public_key (gconstpointer input, gsize n_data,
     329              :                                   gcry_sexp_t *sexp, gchar **comment)
     330              : {
     331              :         EggBuffer buf;
     332              :         const guchar *at;
     333              :         guchar *decoded;
     334              :         gsize n_decoded;
     335              :         gsize offset;
     336              :         gchar *val;
     337              :         gboolean ret;
     338              :         gint state, algo;
     339              :         guint save;
     340           14 :         const guchar *data = input;
     341              : 
     342           14 :         g_return_val_if_fail (data, FALSE);
     343           14 :         g_return_val_if_fail (sexp, FALSE);
     344              : 
     345              :         /* Look for a key line */
     346              :         for (;;) {
     347              :                 /* Eat space at the front */
     348           18 :                 while (n_data > 0 && g_ascii_isspace (data[0])) {
     349            2 :                         ++data;
     350            2 :                         --n_data;
     351              :                 }
     352              : 
     353              :                 /* Not a comment or blank line? Then parse... */
     354           16 :                 if (data[0] != '#')
     355           14 :                         break;
     356              : 
     357              :                 /* Skip to the next line */
     358            2 :                 at = memchr (data, '\n', n_data);
     359            2 :                 if (!at)
     360            0 :                         return GKM_DATA_UNRECOGNIZED;
     361            2 :                 at += 1;
     362            2 :                 n_data -= (at - data);
     363            2 :                 data = at;
     364              :         }
     365              : 
     366              :         /* Limit to use only the first line */
     367           14 :         at = memchr (data, '\n', n_data);
     368           14 :         if (at != NULL)
     369           14 :                 n_data = at - data;
     370              : 
     371              :         /* Find the first space */
     372           14 :         at = memchr (data, ' ', n_data);
     373           14 :         if (!at) {
     374            0 :                 g_message ("SSH public key missing space");
     375            0 :                 return GKM_DATA_UNRECOGNIZED;
     376              :         }
     377              : 
     378              :         /* Parse the key type */
     379           14 :         val = g_strndup ((gchar*)data, at - data);
     380           14 :         algo = keytype_to_algo (val);
     381           14 :         if (!algo) {
     382              :                 /* A number usually means an SSH1 key, just quietly ignore */
     383            0 :                 if (atoi (val) == 0)
     384            0 :                         g_message ("Unsupported or unknown SSH key algorithm: %s", val);
     385              :         }
     386           14 :         g_free (val);
     387           14 :         if (!algo)
     388            0 :                 return GKM_DATA_UNRECOGNIZED;
     389              : 
     390              :         /* Skip more whitespace */
     391           14 :         n_data -= (at - data);
     392           14 :         data = at;
     393           30 :         while (n_data > 0 && (data[0] == ' ' || data[0] == '\t')) {
     394           16 :                 ++data;
     395           16 :                 --n_data;
     396              :         }
     397              : 
     398              :         /* Find the next whitespace, or the end */
     399           14 :         at = memchr (data, ' ', n_data);
     400           14 :         if (at == NULL)
     401            6 :                 at = data + n_data;
     402              : 
     403              :         /* Decode the base64 key */
     404           14 :         save = state = 0;
     405           14 :         decoded = g_malloc (n_data * 3 / 4);
     406           14 :         n_decoded = g_base64_decode_step ((gchar*)data, n_data, decoded, &state, &save);
     407              : 
     408              :         /* Parse the actual key */
     409           14 :         egg_buffer_init_static (&buf, decoded, n_decoded);
     410           14 :         offset = 0;
     411           14 :         ret = read_public (&buf, &offset, sexp, NULL);
     412           14 :         g_free (decoded);
     413           14 :         if (!ret) {
     414            0 :                 g_message ("failed to parse base64 part of SSH key");
     415            0 :                 return GKM_DATA_FAILURE;
     416              :         }
     417              : 
     418              :         /* Skip more whitespace */
     419           14 :         n_data -= (at - data);
     420           14 :         data = at;
     421           24 :         while (n_data > 0 && (data[0] == ' ' || data[0] == '\t')) {
     422           10 :                 ++data;
     423           10 :                 --n_data;
     424              :         }
     425              : 
     426              :         /* If there's data left, its the comment */
     427           14 :         if (comment)
     428           14 :                 *comment = n_data ? g_strndup ((gchar*)data, n_data) : NULL;
     429              : 
     430           14 :         return GKM_DATA_SUCCESS;
     431              : }
     432              : 
     433              : GkmDataResult
     434           20 : gkm_ssh_openssh_parse_private_key (GBytes *data,
     435              :                                    const gchar *password,
     436              :                                    gssize n_password,
     437              :                                    gcry_sexp_t *sexp)
     438              : {
     439              :         ParsePrivate ctx;
     440              :         guint num;
     441              : 
     442           20 :         memset (&ctx, 0, sizeof (ctx));
     443           20 :         ctx.result = FALSE;
     444           20 :         ctx.seen = FALSE;
     445           20 :         ctx.sexp = NULL;
     446           20 :         ctx.password = password;
     447           20 :         ctx.n_password = n_password;
     448              : 
     449           20 :         num = egg_armor_parse (data, parsed_pem_block, &ctx);
     450              : 
     451              :         /* Didn't find any private key there */
     452           20 :         if (num == 0 || !ctx.seen) {
     453            0 :                 g_message ("no private keys found in file");
     454            0 :                 return GKM_DATA_UNRECOGNIZED;
     455              :         }
     456              : 
     457           20 :         *sexp = ctx.sexp;
     458           20 :         return ctx.result;
     459              : }
     460              : 
     461              : gchar *
     462            2 : gkm_ssh_openssh_digest_private_key (GBytes *data)
     463              : {
     464            2 :         gchar *result = NULL;
     465            2 :         egg_armor_parse (data, digest_pem_block, &result);
     466            2 :         return result;
     467              : }
        

Generated by: LCOV version 2.0-1