common: Minor change of hex2str to allow for embedded nul.
[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 <http://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.  The length of
69    the fingerprint is returned at R_LEN and the fingerprint itself at
70    R_FPR.  In case of a error code is returned and NULL stored at
71    R_FPR.  */
72 static gpg_error_t
73 get_fingerprint (gcry_sexp_t key, void **r_fpr, size_t *r_len, int as_string)
74 {
75   gpg_error_t err;
76   gcry_sexp_t list = NULL;
77   gcry_sexp_t l2 = NULL;
78   const char *s;
79   char *name = NULL;
80   int idx;
81   const char *elems;
82   gcry_md_hd_t md = NULL;
83   int blobmode = 0;
84
85   *r_fpr = NULL;
86   *r_len = 0;
87
88   /* Check that the first element is valid. */
89   list = gcry_sexp_find_token (key, "public-key", 0);
90   if (!list)
91     list = gcry_sexp_find_token (key, "private-key", 0);
92   if (!list)
93     list = gcry_sexp_find_token (key, "protected-private-key", 0);
94   if (!list)
95     list = gcry_sexp_find_token (key, "shadowed-private-key", 0);
96   if (!list)
97     {
98       err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_SEXP);
99       goto leave;
100     }
101
102   l2 = gcry_sexp_cadr (list);
103   gcry_sexp_release (list);
104   list = l2;
105   l2 = NULL;
106
107   name = gcry_sexp_nth_string (list, 0);
108   if (!name)
109     {
110       err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
111       goto leave;
112     }
113
114   err = gcry_md_open (&md, GCRY_MD_MD5, 0);
115   if (err)
116     goto leave;
117
118   switch (gcry_pk_map_name (name))
119     {
120     case GCRY_PK_RSA:
121       elems = "en";
122       gcry_md_write (md, "\0\0\0\x07ssh-rsa", 11);
123       break;
124
125     case GCRY_PK_DSA:
126       elems = "pqgy";
127       gcry_md_write (md, "\0\0\0\x07ssh-dss", 11);
128       break;
129
130     case GCRY_PK_ECC:
131       if (is_eddsa (list))
132         {
133           elems = "q";
134           blobmode = 1;
135           /* For now there is just one curve, thus no need to switch
136              on it.  */
137           gcry_md_write (md, "\0\0\0\x0b" "ssh-ed25519", 15);
138         }
139       else
140         {
141           /* We only support the 3 standard curves for now.  It is
142              just a quick hack.  */
143           elems = "q";
144           gcry_md_write (md, "\0\0\0\x13" "ecdsa-sha2-nistp", 20);
145           l2 = gcry_sexp_find_token (list, "curve", 0);
146           if (!l2)
147             elems = "";
148           else
149             {
150               gcry_free (name);
151               name = gcry_sexp_nth_string (l2, 1);
152               gcry_sexp_release (l2);
153               l2 = NULL;
154               if (!name)
155                 elems = "";
156               else if (!strcmp (name, "NIST P-256")||!strcmp (name, "nistp256"))
157                 gcry_md_write (md, "256\0\0\0\x08nistp256", 15);
158               else if (!strcmp (name, "NIST P-384")||!strcmp (name, "nistp384"))
159                 gcry_md_write (md, "384\0\0\0\x08nistp521", 15);
160               else if (!strcmp (name, "NIST P-521")||!strcmp (name, "nistp521"))
161                 gcry_md_write (md, "521\0\0\0\x08nistp521", 15);
162               else
163                 elems = "";
164             }
165           if (!*elems)
166             err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_CURVE);
167         }
168       break;
169
170     default:
171       elems = "";
172       err = gpg_err_make (default_errsource, GPG_ERR_PUBKEY_ALGO);
173       break;
174     }
175   if (err)
176     goto leave;
177
178
179   for (idx = 0, s = elems; *s; s++, idx++)
180     {
181       l2 = gcry_sexp_find_token (list, s, 1);
182       if (!l2)
183         {
184           err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
185           goto leave;
186         }
187       if (blobmode)
188         {
189           const char *blob;
190           size_t bloblen;
191           unsigned char lenbuf[4];
192
193           blob = gcry_sexp_nth_data (l2, 1, &bloblen);
194           if (!blob)
195             {
196               err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
197               goto leave;
198             }
199           lenbuf[0] = bloblen >> 24;
200           lenbuf[1] = bloblen >> 16;
201           lenbuf[2] = bloblen >>  8;
202           lenbuf[3] = bloblen;
203           gcry_md_write (md, lenbuf, 4);
204           gcry_md_write (md, blob, bloblen);
205         }
206       else
207         {
208           gcry_mpi_t a;
209           unsigned char *buf;
210           size_t buflen;
211
212           a = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
213           gcry_sexp_release (l2);
214           l2 = NULL;
215           if (!a)
216             {
217               err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
218               goto leave;
219             }
220
221           err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
222           gcry_mpi_release (a);
223           if (err)
224             goto leave;
225           gcry_md_write (md, buf, buflen);
226           gcry_free (buf);
227         }
228     }
229
230   *r_fpr = gcry_malloc (as_string? 61:20);
231   if (!*r_fpr)
232     {
233       err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
234       goto leave;
235     }
236
237   if (as_string)
238     {
239       bin2hexcolon (gcry_md_read (md, GCRY_MD_MD5), 16, *r_fpr);
240       *r_len = 3*16+1;
241       strlwr (*r_fpr);
242     }
243   else
244     {
245       memcpy (*r_fpr, gcry_md_read (md, GCRY_MD_MD5), 16);
246       *r_len = 16;
247     }
248   err = 0;
249
250  leave:
251   gcry_free (name);
252   gcry_sexp_release (l2);
253   gcry_md_close (md);
254   gcry_sexp_release (list);
255   return err;
256 }
257
258 /* Return the Secure Shell type fingerprint for KEY.  The length of
259    the fingerprint is returned at R_LEN and the fingerprint itself at
260    R_FPR.  In case of an error an error code is returned and NULL
261    stored at R_FPR.  */
262 gpg_error_t
263 ssh_get_fingerprint (gcry_sexp_t key, void **r_fpr, size_t *r_len)
264 {
265   return get_fingerprint (key, r_fpr, r_len, 0);
266 }
267
268
269 /* Return the Secure Shell type fingerprint for KEY as a string.  The
270    fingerprint is mallcoed and stored at R_FPRSTR.  In case of an
271    error an error code is returned and NULL stored at R_FPRSTR.  */
272 gpg_error_t
273 ssh_get_fingerprint_string (gcry_sexp_t key, char **r_fprstr)
274 {
275   gpg_error_t err;
276   size_t dummy;
277   void *string;
278
279   err = get_fingerprint (key, &string, &dummy, 1);
280   *r_fprstr = string;
281   return err;
282 }