4 * Copyright (C) 2011 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as
8 * published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21 * Author: Stef Walter <stefw@collabora.co.uk>
26 #include "gcr-openssh.h"
27 #include "gcr-internal.h"
28 #include "gcr-types.h"
30 #include "egg/egg-buffer.h"
31 #include "egg/egg-decimal.h"
33 #include "pkcs11/pkcs11.h"
38 GcrOpensshPubCallback callback;
43 skip_spaces (const gchar ** line,
46 while (*n_line > 0 && (*line)[0] == ' ') {
53 next_word (const gchar **line,
63 skip_spaces (line, n_line);
101 match_word (const gchar *word,
103 const gchar *matches)
105 gsize len = strlen (matches);
108 return memcmp (word, matches, n_word) == 0;
112 keytype_to_algo (const gchar *algo,
117 else if (match_word (algo, length, "ssh-rsa"))
119 else if (match_word (algo, length, "ssh-dss"))
125 read_decimal_mpi (const gchar *decimal,
127 GckAttributes *attrs,
128 gulong attribute_type)
133 data = egg_decimal_decode (decimal, n_decimal, &n_data);
137 gck_attributes_add_data (attrs, attribute_type, data, n_data);
142 atoin (const char *p, gint digits)
144 gint ret = 0, base = 1;
145 while(--digits >= 0) {
146 if (p[digits] < '0' || p[digits] > '9')
148 ret += (p[digits] - '0') * base;
155 parse_v1_public_line (const gchar *line,
157 GcrOpensshPubCallback callback,
160 const gchar *word_bits, *word_exponent, *word_modulus, *word_options, *outer;
161 gsize len_bits, len_exponent, len_modulus, len_options, n_outer;
162 GckAttributes *attrs;
163 gchar *label, *options;
173 /* Eat space at the front */
174 skip_spaces (&line, &length);
176 /* Blank line or comment */
177 if (length == 0 || line[0] == '#')
178 return GCR_ERROR_UNRECOGNIZED;
181 * If the line starts with a digit, then no options:
183 * 2048 35 25213680043....93533757 Label
185 * If the line doesn't start with a digit, then have options:
187 * option,option 2048 35 25213680043....93533757 Label
189 if (g_ascii_isdigit (line[0])) {
193 if (!next_word (&line, &length, &word_options, &len_options))
194 return GCR_ERROR_UNRECOGNIZED;
197 if (!next_word (&line, &length, &word_bits, &len_bits) ||
198 !next_word (&line, &length, &word_exponent, &len_exponent) ||
199 !next_word (&line, &length, &word_modulus, &len_modulus))
200 return GCR_ERROR_UNRECOGNIZED;
202 bits = atoin (word_bits, len_bits);
204 return GCR_ERROR_UNRECOGNIZED;
206 attrs = gck_attributes_new ();
208 if (!read_decimal_mpi (word_exponent, len_exponent, attrs, CKA_PUBLIC_EXPONENT) ||
209 !read_decimal_mpi (word_modulus, len_modulus, attrs, CKA_MODULUS)) {
210 gck_attributes_unref (attrs);
211 return GCR_ERROR_UNRECOGNIZED;
214 gck_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_RSA);
215 gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
217 skip_spaces (&line, &length);
219 label = g_strndup (line, length);
221 gck_attributes_add_string (attrs, CKA_LABEL, label);
225 options = g_strndup (word_options, len_options);
227 if (callback != NULL)
228 (callback) (attrs, label, options, outer, n_outer, user_data);
230 gck_attributes_unref (attrs);
237 read_buffer_mpi (EggBuffer *buffer,
239 GckAttributes *attrs,
240 gulong attribute_type)
245 if (!egg_buffer_get_byte_array (buffer, *offset, offset, &data, &len))
248 gck_attributes_add_data (attrs, attribute_type, data, len);
252 static GckAttributes *
253 read_v2_public_dsa (EggBuffer *buffer,
256 GckAttributes *attrs;
258 attrs = gck_attributes_new ();
260 if (!read_buffer_mpi (buffer, offset, attrs, CKA_PRIME) ||
261 !read_buffer_mpi (buffer, offset, attrs, CKA_SUBPRIME) ||
262 !read_buffer_mpi (buffer, offset, attrs, CKA_BASE) ||
263 !read_buffer_mpi (buffer, offset, attrs, CKA_VALUE)) {
264 gck_attributes_unref (attrs);
268 gck_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_DSA);
269 gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
274 static GckAttributes *
275 read_v2_public_rsa (EggBuffer *buffer,
278 GckAttributes *attrs;
280 attrs = gck_attributes_new ();
282 if (!read_buffer_mpi (buffer, offset, attrs, CKA_PUBLIC_EXPONENT) ||
283 !read_buffer_mpi (buffer, offset, attrs, CKA_MODULUS)) {
284 gck_attributes_unref (attrs);
288 gck_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_RSA);
289 gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
294 static GckAttributes *
295 read_v2_public_key (gulong algo,
299 GckAttributes *attrs;
305 egg_buffer_init_static (&buffer, data, n_data);
308 /* The string algorithm */
309 if (!egg_buffer_get_string (&buffer, offset, &offset,
310 &stype, (EggBufferAllocator)g_realloc))
313 alg = keytype_to_algo (stype, stype ? strlen (stype) : 0);
317 g_message ("invalid or mis-matched algorithm in ssh public key: %s", stype);
318 egg_buffer_uninit (&buffer);
324 attrs = read_v2_public_rsa (&buffer, &offset);
327 attrs = read_v2_public_dsa (&buffer, &offset);
330 g_assert_not_reached ();
334 egg_buffer_uninit (&buffer);
338 static GckAttributes *
339 decode_v2_public_key (gulong algo,
343 GckAttributes *attrs;
349 /* Decode the base64 key */
351 decoded = g_malloc (n_data * 3 / 4);
352 n_decoded = g_base64_decode_step ((gchar*)data, n_data, decoded, &state, &save);
359 /* Parse the actual key */
360 attrs = read_v2_public_key (algo, decoded, n_decoded);
368 parse_v2_public_line (const gchar *line,
370 GcrOpensshPubCallback callback,
373 const gchar *word_options, *word_algo, *word_key;
374 gsize len_options, len_algo, len_key;
375 GckAttributes *attrs;
378 const gchar *outer = line;
379 gsize n_outer = length;
384 /* Eat space at the front */
385 skip_spaces (&line, &length);
387 /* Blank line or comment */
388 if (length == 0 || line[0] == '#')
389 return GCR_ERROR_UNRECOGNIZED;
391 if (!next_word (&line, &length, &word_algo, &len_algo))
392 return GCR_ERROR_UNRECOGNIZED;
395 * If the first word is not the algorithm, then we have options:
397 * option,option ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAI...EAz8Ji= Label here
399 * If the first word is the algorithm, then we have no options:
401 * ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAI...EAz8Ji= Label here
403 algo = keytype_to_algo (word_algo, len_algo);
404 if (algo == G_MAXULONG) {
405 word_options = word_algo;
406 len_options = len_algo;
407 if (!next_word (&line, &length, &word_algo, &len_algo))
408 return GCR_ERROR_UNRECOGNIZED;
409 algo = keytype_to_algo (word_algo, len_algo);
410 if (algo == G_MAXULONG)
411 return GCR_ERROR_UNRECOGNIZED;
417 /* Must have at least two words */
418 if (!next_word (&line, &length, &word_key, &len_key))
419 return GCR_ERROR_FAILURE;
421 attrs = decode_v2_public_key (algo, word_key, len_key);
423 return GCR_ERROR_FAILURE;
426 options = g_strndup (word_options, len_options);
430 /* The remainder of the line is the label */
431 skip_spaces (&line, &length);
433 label = g_strndup (line, length);
435 gck_attributes_add_string (attrs, CKA_LABEL, label);
438 if (callback != NULL)
439 (callback) (attrs, label, options, outer, n_outer, user_data);
441 gck_attributes_unref (attrs);
448 _gcr_openssh_pub_parse (gconstpointer data,
450 GcrOpensshPubCallback callback,
460 g_return_val_if_fail (data, FALSE);
468 end = memchr (line, '\n', length);
475 res = parse_v2_public_line (line, end - line, callback, user_data);
476 if (res == GCR_ERROR_UNRECOGNIZED)
477 res = parse_v1_public_line (line, end - line, callback, user_data);
478 if (res == GCR_SUCCESS)
486 length -= (end - line);