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
is_eddsa(gcry_sexp_t keyparms)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 /* Dummy functions for es_mopen. */
dummy_realloc(void * mem,size_t size)68 static void *dummy_realloc (void *mem, size_t size) { (void) size; return mem; }
dummy_free(void * mem)69 static void dummy_free (void *mem) { (void) mem; }
70
71 /* Return the Secure Shell type fingerprint for KEY using digest ALGO.
72 The length of the fingerprint is returned at R_LEN and the
73 fingerprint itself at R_FPR. In case of a error code is returned
74 and NULL stored at R_FPR. */
75 static gpg_error_t
get_fingerprint(gcry_sexp_t key,int algo,void ** r_fpr,size_t * r_len,int as_string)76 get_fingerprint (gcry_sexp_t key, int algo,
77 void **r_fpr, size_t *r_len, int as_string)
78 {
79 gpg_error_t err;
80 gcry_sexp_t list = NULL;
81 gcry_sexp_t l2 = NULL;
82 const char *s;
83 char *name = NULL;
84 int idx;
85 const char *elems;
86 gcry_md_hd_t md = NULL;
87 int blobmode = 0;
88
89 *r_fpr = NULL;
90 *r_len = 0;
91
92 /* Check that the first element is valid. */
93 list = gcry_sexp_find_token (key, "public-key", 0);
94 if (!list)
95 list = gcry_sexp_find_token (key, "private-key", 0);
96 if (!list)
97 list = gcry_sexp_find_token (key, "protected-private-key", 0);
98 if (!list)
99 list = gcry_sexp_find_token (key, "shadowed-private-key", 0);
100 if (!list)
101 {
102 err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_SEXP);
103 goto leave;
104 }
105
106 l2 = gcry_sexp_cadr (list);
107 gcry_sexp_release (list);
108 list = l2;
109 l2 = NULL;
110
111 name = gcry_sexp_nth_string (list, 0);
112 if (!name)
113 {
114 err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
115 goto leave;
116 }
117
118 err = gcry_md_open (&md, algo, 0);
119 if (err)
120 goto leave;
121
122 switch (gcry_pk_map_name (name))
123 {
124 case GCRY_PK_RSA:
125 elems = "en";
126 gcry_md_write (md, "\0\0\0\x07ssh-rsa", 11);
127 break;
128
129 case GCRY_PK_DSA:
130 elems = "pqgy";
131 gcry_md_write (md, "\0\0\0\x07ssh-dss", 11);
132 break;
133
134 case GCRY_PK_ECC:
135 if (is_eddsa (list))
136 {
137 elems = "q";
138 blobmode = 1;
139 /* For now there is just one curve, thus no need to switch
140 on it. */
141 gcry_md_write (md, "\0\0\0\x0b" "ssh-ed25519", 15);
142 }
143 else
144 {
145 /* We only support the 3 standard curves for now. It is
146 just a quick hack. */
147 elems = "q";
148 gcry_md_write (md, "\0\0\0\x13" "ecdsa-sha2-nistp", 20);
149 l2 = gcry_sexp_find_token (list, "curve", 0);
150 if (!l2)
151 elems = "";
152 else
153 {
154 gcry_free (name);
155 name = gcry_sexp_nth_string (l2, 1);
156 gcry_sexp_release (l2);
157 l2 = NULL;
158 if (!name)
159 elems = "";
160 else if (!strcmp (name, "NIST P-256")||!strcmp (name, "nistp256"))
161 gcry_md_write (md, "256\0\0\0\x08nistp256", 15);
162 else if (!strcmp (name, "NIST P-384")||!strcmp (name, "nistp384"))
163 gcry_md_write (md, "384\0\0\0\x08nistp384", 15);
164 else if (!strcmp (name, "NIST P-521")||!strcmp (name, "nistp521"))
165 gcry_md_write (md, "521\0\0\0\x08nistp521", 15);
166 else
167 elems = "";
168 }
169 if (!*elems)
170 err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_CURVE);
171 }
172 break;
173
174 default:
175 elems = "";
176 err = gpg_err_make (default_errsource, GPG_ERR_PUBKEY_ALGO);
177 break;
178 }
179 if (err)
180 goto leave;
181
182
183 for (idx = 0, s = elems; *s; s++, idx++)
184 {
185 l2 = gcry_sexp_find_token (list, s, 1);
186 if (!l2)
187 {
188 err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
189 goto leave;
190 }
191 if (blobmode)
192 {
193 const char *blob;
194 size_t bloblen;
195 unsigned char lenbuf[4];
196
197 blob = gcry_sexp_nth_data (l2, 1, &bloblen);
198 if (!blob)
199 {
200 err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
201 goto leave;
202 }
203 blob++;
204 bloblen--;
205 lenbuf[0] = bloblen >> 24;
206 lenbuf[1] = bloblen >> 16;
207 lenbuf[2] = bloblen >> 8;
208 lenbuf[3] = bloblen;
209 gcry_md_write (md, lenbuf, 4);
210 gcry_md_write (md, blob, bloblen);
211 }
212 else
213 {
214 gcry_mpi_t a;
215 unsigned char *buf;
216 size_t buflen;
217
218 a = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
219 gcry_sexp_release (l2);
220 l2 = NULL;
221 if (!a)
222 {
223 err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP);
224 goto leave;
225 }
226
227 err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
228 gcry_mpi_release (a);
229 if (err)
230 goto leave;
231 gcry_md_write (md, buf, buflen);
232 gcry_free (buf);
233 }
234 }
235
236 if (as_string)
237 {
238 const char *algo_name;
239 char *fpr;
240
241 /* Prefix string with the algorithm name and a colon. */
242 algo_name = gcry_md_algo_name (algo);
243 *r_fpr = xtrymalloc (strlen (algo_name) + 1 + 3 * gcry_md_get_algo_dlen (algo) + 1);
244 if (*r_fpr == NULL)
245 {
246 err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
247 goto leave;
248 }
249
250 memcpy (*r_fpr, algo_name, strlen (algo_name));
251 fpr = (char *) *r_fpr + strlen (algo_name);
252 *fpr++ = ':';
253
254 if (algo == GCRY_MD_MD5)
255 {
256 bin2hexcolon (gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo), fpr);
257 strlwr (fpr);
258 }
259 else
260 {
261 struct b64state b64s;
262 estream_t stream;
263 char *p;
264 long int len;
265
266 /* Write the base64-encoded hash to fpr. */
267 stream = es_mopen (fpr, 3 * gcry_md_get_algo_dlen (algo) + 1, 0,
268 0, dummy_realloc, dummy_free, "w");
269 if (stream == NULL)
270 {
271 err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
272 goto leave;
273 }
274
275 err = b64enc_start_es (&b64s, stream, "");
276 if (err)
277 {
278 es_fclose (stream);
279 goto leave;
280 }
281
282 err = b64enc_write (&b64s,
283 gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo));
284 if (err)
285 {
286 es_fclose (stream);
287 goto leave;
288 }
289
290 /* Finish, get the length, and close the stream. */
291 err = b64enc_finish (&b64s);
292 len = es_ftell (stream);
293 es_fclose (stream);
294 if (err)
295 goto leave;
296
297 /* Terminate. */
298 fpr[len] = 0;
299
300 /* Strip the trailing padding characters. */
301 for (p = fpr + len - 1; p > fpr && *p == '='; p--)
302 *p = 0;
303 }
304
305 *r_len = strlen (*r_fpr) + 1;
306 }
307 else
308 {
309 *r_len = gcry_md_get_algo_dlen (algo);
310 *r_fpr = xtrymalloc (*r_len);
311 if (!*r_fpr)
312 {
313 err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
314 goto leave;
315 }
316 memcpy (*r_fpr, gcry_md_read (md, algo), *r_len);
317 }
318 err = 0;
319
320 leave:
321 gcry_free (name);
322 gcry_sexp_release (l2);
323 gcry_md_close (md);
324 gcry_sexp_release (list);
325 return err;
326 }
327
328 /* Return the Secure Shell type fingerprint for KEY using digest ALGO.
329 The length of the fingerprint is returned at R_LEN and the
330 fingerprint itself at R_FPR. In case of an error an error code is
331 returned and NULL stored at R_FPR. */
332 gpg_error_t
ssh_get_fingerprint(gcry_sexp_t key,int algo,void ** r_fpr,size_t * r_len)333 ssh_get_fingerprint (gcry_sexp_t key, int algo,
334 void **r_fpr, size_t *r_len)
335 {
336 return get_fingerprint (key, algo, r_fpr, r_len, 0);
337 }
338
339
340 /* Return the Secure Shell type fingerprint for KEY using digest ALGO
341 as a string. The fingerprint is mallcoed and stored at R_FPRSTR.
342 In case of an error an error code is returned and NULL stored at
343 R_FPRSTR. */
344 gpg_error_t
ssh_get_fingerprint_string(gcry_sexp_t key,int algo,char ** r_fprstr)345 ssh_get_fingerprint_string (gcry_sexp_t key, int algo, char **r_fprstr)
346 {
347 gpg_error_t err;
348 size_t dummy;
349 void *string;
350
351 err = get_fingerprint (key, algo, &string, &dummy, 1);
352 *r_fprstr = string;
353 return err;
354 }
355