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 : }
|