common: Support different digest algorithms for ssh fingerprints.
[gnupg.git] / common / ssh-utils.c
1 /* ssh-utils.c - Secure Shell helper functions
2  * Copyright (C) 2011 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * This file is free software; you can redistribute it and/or modify
7  * it under the terms of either
8  *
9  *   - the GNU Lesser General Public License as published by the Free
10  *     Software Foundation; either version 3 of the License, or (at
11  *     your option) any later version.
12  *
13  * or
14  *
15  *   - the GNU General Public License as published by the Free
16  *     Software Foundation; either version 2 of the License, or (at
17  *     your option) any later version.
18  *
19  * or both in parallel, as here.
20  *
21  * This file is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, see <https://www.gnu.org/licenses/>.
28  */
29
30 #include <config.h>
31 #include <stdlib.h>
32 #include <errno.h>
33 #include <ctype.h>
34 #include <assert.h>
35
36 #include "util.h"
37 #include "ssh-utils.h"
38
39
40 /* Return true if KEYPARMS holds an EdDSA key.  */
41 static int
42 is_eddsa (gcry_sexp_t keyparms)
43 {
44   int result = 0;
45   gcry_sexp_t list;
46   const char *s;
47   size_t n;
48   int i;
49
50   list = gcry_sexp_find_token (keyparms, "flags", 0);
51   for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--)
52     {
53       s = gcry_sexp_nth_data (list, i, &n);
54       if (!s)
55         continue; /* Not a data element. */
56
57       if (n == 5 && !memcmp (s, "eddsa", 5))
58         {
59           result = 1;
60           break;
61         }
62     }
63   gcry_sexp_release (list);
64   return result;
65 }
66
67
68 /* Return the Secure Shell type fingerprint for KEY using digest ALGO.
69    The length of the fingerprint is returned at R_LEN and the
70    fingerprint itself at R_FPR.  In case of a error code is returned
71    and NULL stored at R_FPR.  */
72 static gpg_error_t
73 get_fingerprint (gcry_sexp_t key, int algo,
74                  void **r_fpr, size_t *r_len, int as_string)
75 {
76   gpg_error_t err;
77   gcry_sexp_t list = NULL;
78   gcry_sexp_t l2 = NULL;
79   const char *s;
80   char *name = NULL;
81   int idx;
82   const char *elems;
83   gcry_md_hd_t md = NULL;
84   int blobmode = 0;
85
86   *r_fpr = NULL;
87   *r_len = 0;
88
89   /* Check that the first element is valid. */
90   list = gcry_sexp_find_token (key, "public-key", 0);
91   if (!list)
92     list = gcry_sexp_find_token (key, "private-key", 0);
93   if (!list)
94     list = gcry_sexp_find_token (key, "protected-private-key", 0);
95   if (!list)
96     list = gcry_sexp_find_token (key, "shadowed-private-key", 0);
97   if (!list)
98     {
99       err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_SEXP);
100       goto leave;
101     }
102
103   l2 = gcry_sexp_cadr (list);
104   gcry_sexp_release (list);
105   list = l2;
106   l2 = NULL;
107
108   name = gcry_sexp_nth_string (list, 0);
109   if (!name)
110     {
111       err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
112       goto leave;
113     }
114
115   err = gcry_md_open (&md, algo, 0);
116   if (err)
117     goto leave;
118
119   switch (gcry_pk_map_name (name))
120     {
121     case GCRY_PK_RSA:
122       elems = "en";
123       gcry_md_write (md, "\0\0\0\x07ssh-rsa", 11);
124       break;
125
126     case GCRY_PK_DSA:
127       elems = "pqgy";
128       gcry_md_write (md, "\0\0\0\x07ssh-dss", 11);
129       break;
130
131     case GCRY_PK_ECC:
132       if (is_eddsa (list))
133         {
134           elems = "q";
135           blobmode = 1;
136           /* For now there is just one curve, thus no need to switch
137              on it.  */
138           gcry_md_write (md, "\0\0\0\x0b" "ssh-ed25519", 15);
139         }
140       else
141         {
142           /* We only support the 3 standard curves for now.  It is
143              just a quick hack.  */
144           elems = "q";
145           gcry_md_write (md, "\0\0\0\x13" "ecdsa-sha2-nistp", 20);
146           l2 = gcry_sexp_find_token (list, "curve", 0);
147           if (!l2)
148             elems = "";
149           else
150             {
151               gcry_free (name);
152               name = gcry_sexp_nth_string (l2, 1);
153               gcry_sexp_release (l2);
154               l2 = NULL;
155               if (!name)
156                 elems = "";
157               else if (!strcmp (name, "NIST P-256")||!strcmp (name, "nistp256"))
158                 gcry_md_write (md, "256\0\0\0\x08nistp256", 15);
159               else if (!strcmp (name, "NIST P-384")||!strcmp (name, "nistp384"))
160                 gcry_md_write (md, "384\0\0\0\x08nistp384", 15);
161               else if (!strcmp (name, "NIST P-521")||!strcmp (name, "nistp521"))
162                 gcry_md_write (md, "521\0\0\0\x08nistp521", 15);
163               else
164                 elems = "";
165             }
166           if (!*elems)
167             err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_CURVE);
168         }
169       break;
170
171     default:
172       elems = "";
173       err = gpg_err_make (default_errsource, GPG_ERR_PUBKEY_ALGO);
174       break;
175     }
176   if (err)
177     goto leave;
178
179
180   for (idx = 0, s = elems; *s; s++, idx++)
181     {
182       l2 = gcry_sexp_find_token (list, s, 1);
183       if (!l2)
184         {
185           err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
186           goto leave;
187         }
188       if (blobmode)
189         {
190           const char *blob;
191           size_t bloblen;
192           unsigned char lenbuf[4];
193
194           blob = gcry_sexp_nth_data (l2, 1, &bloblen);
195           if (!blob)
196             {
197               err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
198               goto leave;
199             }
200           blob++;
201           bloblen--;
202           lenbuf[0] = bloblen >> 24;
203           lenbuf[1] = bloblen >> 16;
204           lenbuf[2] = bloblen >>  8;
205           lenbuf[3] = bloblen;
206           gcry_md_write (md, lenbuf, 4);
207           gcry_md_write (md, blob, bloblen);
208         }
209       else
210         {
211           gcry_mpi_t a;
212           unsigned char *buf;
213           size_t buflen;
214
215           a = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
216           gcry_sexp_release (l2);
217           l2 = NULL;
218           if (!a)
219             {
220               err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
221               goto leave;
222             }
223
224           err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
225           gcry_mpi_release (a);
226           if (err)
227             goto leave;
228           gcry_md_write (md, buf, buflen);
229           gcry_free (buf);
230         }
231     }
232
233   if (as_string)
234     {
235       *r_fpr = (algo == GCRY_MD_MD5 ? bin2hexcolon : /* XXX we need base64 */ bin2hex)
236         (gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo), NULL);
237       *r_len = strlen (*r_fpr) + 1;
238       strlwr (*r_fpr);
239     }
240   else
241     {
242       *r_len = gcry_md_get_algo_dlen (algo);
243       *r_fpr = xtrymalloc (*r_len);
244       if (!*r_fpr)
245         {
246           err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
247           goto leave;
248         }
249       memcpy (*r_fpr, gcry_md_read (md, algo), *r_len);
250     }
251   err = 0;
252
253  leave:
254   gcry_free (name);
255   gcry_sexp_release (l2);
256   gcry_md_close (md);
257   gcry_sexp_release (list);
258   return err;
259 }
260
261 /* Return the Secure Shell type fingerprint for KEY using digest ALGO.
262    The length of the fingerprint is returned at R_LEN and the
263    fingerprint itself at R_FPR.  In case of an error an error code is
264    returned and NULL stored at R_FPR.  */
265 gpg_error_t
266 ssh_get_fingerprint (gcry_sexp_t key, int algo,
267                      void **r_fpr, size_t *r_len)
268 {
269   return get_fingerprint (key, algo, r_fpr, r_len, 0);
270 }
271
272
273 /* Return the Secure Shell type fingerprint for KEY using digest ALGO
274    as a string.  The fingerprint is mallcoed and stored at R_FPRSTR.
275    In case of an error an error code is returned and NULL stored at
276    R_FPRSTR.  */
277 gpg_error_t
278 ssh_get_fingerprint_string (gcry_sexp_t key, int algo, char **r_fprstr)
279 {
280   gpg_error_t err;
281   size_t dummy;
282   void *string;
283
284   err = get_fingerprint (key, algo, &string, &dummy, 1);
285   *r_fprstr = string;
286   return err;
287 }