]> git.tuebingen.mpg.de Git - paraslash.git/blob - crypt_common.c
paraslash 0.7.3
[paraslash.git] / crypt_common.c
1 /* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
2
3 /** \file crypt_common.c Crypto functions independent of openssl/libgcrypt. */
4
5 #include <regex.h>
6
7 #include "para.h"
8 #include "error.h"
9 #include "string.h"
10 #include "crypt.h"
11 #include "crypt_backend.h"
12 #include "portable_io.h"
13 #include "fd.h"
14 #include "base64.h"
15
16 /** If the key begins with this text, we treat it as an ssh key. */
17 #define KEY_TYPE_TXT "ssh-rsa"
18
19 /*
20  * Check if the given buffer starts with an ssh rsa key signature.
21  *
22  * Returns number of header bytes to be skipped on success, zero if no ssh rsa
23  * signature was found.
24  */
25 static size_t is_ssh_rsa_key(char *data, size_t size)
26 {
27         char *cp;
28
29         if (size < strlen(KEY_TYPE_TXT) + 2)
30                 return 0;
31         cp = memchr(data, ' ', size);
32         if (cp == NULL)
33                 return 0;
34         if (strncmp(KEY_TYPE_TXT, data, strlen(KEY_TYPE_TXT)))
35                 return 0;
36         cp++;
37         if (cp >= data + size)
38                 return 0;
39         if (*cp == '\0')
40                 return 0;
41         return cp - data;
42 }
43
44 /*
45  * Perform some sanity checks on the decoded ssh key.
46  *
47  * This function returns the size of the header. Usually, the header is 11
48  * bytes long: four bytes for the length field, and the string "ssh-rsa".
49  */
50 static int check_ssh_key_header(const unsigned char *blob, int blen)
51 {
52         const unsigned char *p = blob, *end = blob + blen;
53         uint32_t rlen;
54
55         if (p + 4 > end)
56                 return -E_SSH_KEY_HEADER;
57         rlen = read_u32_be(p);
58         p += 4;
59         if (p + rlen < p)
60                 return -E_SSH_KEY_HEADER;
61         if (p + rlen > end)
62                 return -E_SSH_KEY_HEADER;
63         if (rlen < strlen(KEY_TYPE_TXT))
64                 return -E_SSH_KEY_HEADER;
65         PARA_DEBUG_LOG("type: %s, rlen: %u\n", p, rlen);
66         if (strncmp((char *)p, KEY_TYPE_TXT, strlen(KEY_TYPE_TXT)))
67                 return -E_SSH_KEY_HEADER;
68         return 4 + rlen;
69 }
70
71 /**
72  * Perform sanity checks and base64-decode an ssh-rsa key.
73  *
74  * \param filename The public key file (usually id_rsa.pub).
75  * \param blob Pointer to base64-decoded blob is returned here.
76  * \param decoded_size The size of the decoded blob.
77  *
78  * The memory pointed at by the returned blob pointer has to be freed by the
79  * caller.
80  *
81  * \return On success, the offset in bytes of the start of the key values
82  * (modulus, exponent..). This is the number of bytes to skip from the blob
83  * until the start of the first encoded number. On failure, a negative error
84  * code is returned.
85  *
86  * \sa \ref uudecode().
87  */
88 int decode_public_key(const char *filename, unsigned char **blob,
89                 size_t *decoded_size)
90 {
91         int ret, ret2;
92         void *map;
93         size_t map_size;
94
95         ret = mmap_full_file(filename, O_RDONLY, &map, &map_size, NULL);
96         if (ret < 0)
97                 return ret;
98         ret = is_ssh_rsa_key(map, map_size);
99         if (ret == 0) {
100                 ret = -E_SSH_PARSE;
101                 goto unmap;
102         }
103         ret = uudecode(map + ret, map_size - ret, (char **)blob, decoded_size);
104         if (ret < 0)
105                 goto unmap;
106         ret = check_ssh_key_header(*blob, *decoded_size);
107         if (ret < 0)
108                 goto unmap;
109 unmap:
110         ret2 = para_munmap(map, map_size);
111         if (ret >= 0 && ret2 < 0)
112                 ret = ret2;
113         return ret;
114 }
115
116 /**
117  * Check existence and permissions of a private key file.
118  *
119  * \param file The path of the key file.
120  *
121  * This checks whether the file exists and its permissions are restrictive
122  * enough. It is considered an error if we own the file and it is readable for
123  * others.
124  *
125  * \return Standard.
126  */
127 int check_private_key_file(const char *file)
128 {
129         struct stat st;
130
131         if (stat(file, &st) != 0)
132                 return -ERRNO_TO_PARA_ERROR(errno);
133         if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0)
134                 return -E_KEY_PERM;
135         return 1;
136 }
137
138 void hash_to_asc(const unsigned char *hash, char *asc)
139 {
140         int i;
141         const char hexchar[] = "0123456789abcdef";
142
143         for (i = 0; i < HASH_SIZE; i++) {
144                 asc[2 * i] = hexchar[hash[i] >> 4];
145                 asc[2 * i + 1] = hexchar[hash[i] & 0xf];
146         }
147         asc[2 * HASH_SIZE] = '\0';
148 }
149
150 int hash_compare(const unsigned char *h1, const unsigned char *h2)
151 {
152         int i;
153
154         for (i = 0; i < HASH_SIZE; i++) {
155                 if (h1[i] < h2[i])
156                         return -1;
157                 if (h1[i] > h2[i])
158                         return 1;
159         }
160         return 0;
161 }
162
163 void hash2_to_asc(const unsigned char *hash, char *asc)
164 {
165         int i;
166         const char hexchar[] = "0123456789abcdef";
167
168         for (i = 0; i < HASH2_SIZE; i++) {
169                 asc[2 * i] = hexchar[hash[i] >> 4];
170                 asc[2 * i + 1] = hexchar[hash[i] & 0xf];
171         }
172         asc[2 * HASH2_SIZE] = '\0';
173 }
174
175 int hash2_compare(const unsigned char *h1, const unsigned char *h2)
176 {
177         int i;
178
179         for (i = 0; i < HASH2_SIZE; i++) {
180                 if (h1[i] < h2[i])
181                         return -1;
182                 if (h1[i] > h2[i])
183                         return 1;
184         }
185         return 0;
186 }
187
188 /**
189  * Check header of an openssh private key and compute bignum offset.
190  *
191  * \param data The base64-decoded key.
192  * \param len The size of the decoded key.
193  *
194  * Several assumptions are made about the key. Most notably, we only support
195  * single unencrypted keys without comments.
196  *
197  * \return The offset at which the first bignum of the private key (the public
198  * exponent n) starts. Negative error code on failure.
199  */
200 int find_openssh_bignum_offset(const unsigned char *data, int len)
201 {
202         /*
203          * Unencrypted keys without comments always start with the below byte
204          * sequence. See PROTOCOL.key of the openssh package.
205          */
206         static const unsigned char valid_openssh_header[] = {
207                 /* string "openssh-key-v1" */
208                 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2d, 0x6b, 0x65,
209                         0x79, 0x2d, 0x76, 0x31,
210                 /* length of the cipher name */
211                 0x00, 0x00, 0x00, 0x00, 0x04,
212                 /* cipher name: "none" */
213                 0x6e, 0x6f, 0x6e, 0x65,
214                 /* length of the kdfname (only used for encrypted keys) */
215                 0x00, 0x00, 0x00, 0x04,
216                 /* kdfname: "none" */
217                 0x6e, 0x6f, 0x6e, 0x65,
218                 /* length of kdfoptions */
219                 0x00, 0x00, 0x00, 0x00,
220                 /* number of keys */
221                 0x00, 0x00, 0x00, 0x01,
222         };
223         uint32_t val;
224         const unsigned char *p, *end = data + len;
225
226         if (len <= sizeof(valid_openssh_header) + 4)
227                 return -E_OPENSSH_PARSE;
228         if (memcmp(data, valid_openssh_header, sizeof(valid_openssh_header)))
229                 return -E_OPENSSH_PARSE;
230         p = data + sizeof(valid_openssh_header);
231         /* length of public key */
232         val = read_u32_be(p);
233         if (val > end - p - 4)
234                 return -E_OPENSSH_PARSE;
235         p += val + 4;
236         /* length of private key */
237         val = read_u32_be(p);
238         if (val > end - p - 4)
239                 return -E_OPENSSH_PARSE;
240         p += 4;
241         /* two equal random integers ("checkint") */
242         if (p + 8 > end)
243                 return -E_OPENSSH_PARSE;
244         if (read_u32_be(p) != read_u32_be(p + 4))
245                 return -E_OPENSSH_PARSE;
246         p += 8;
247         /* length of name of key type "ssh-rsa" */
248         if (p + 11 > end)
249                 return -E_OPENSSH_PARSE;
250         if (read_u32_be(p) != 7)
251                 return -E_OPENSSH_PARSE;
252         if (memcmp(p + 4, "ssh-rsa", 7))
253                 return -E_OPENSSH_PARSE;
254         p += 11;
255         return p - data;
256 }
257
258 /** Private PEM keys (legacy format) start with this header. */
259 #define PRIVATE_PEM_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
260 /** Private OPENSSH keys (RFC4716) start with this header. */
261 #define PRIVATE_OPENSSH_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----"
262 /** Private PEM keys (legacy format) end with this footer. */
263 #define PRIVATE_PEM_KEY_FOOTER "-----END RSA PRIVATE KEY-----"
264 /** Private OPENSSH keys (RFC4716) end with this footer. */
265 #define PRIVATE_OPENSSH_KEY_FOOTER "-----END OPENSSH PRIVATE KEY-----"
266
267 /**
268  * Decode an openssh-v1 (aka RFC4716) or PEM (aka ASN.1) private key.
269  *
270  * \param key_file The private key file (usually id_rsa).
271  * \param result Pointer to base64-decoded blob is returned here.
272  * \param blob_size The size of the decoded blob.
273  *
274  * This only checks header and footer and base64-decodes the part in between.
275  * No attempt to read the decoded part is made.
276  *
277  * \return Negative on errors, PKT_PEM or PKT_OPENSSH on success, indicating
278  * the type of key.
279  */
280 int decode_private_key(const char *key_file, unsigned char **result,
281                 size_t *blob_size)
282 {
283         int ret, ret2, i, j, key_type;
284         void *map;
285         size_t map_size, key_size;
286         unsigned char *blob = NULL;
287         char *begin, *footer, *key;
288
289         ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
290         if (ret < 0)
291                 goto out;
292         ret = -E_KEY_MARKER;
293         if (strncmp(map, PRIVATE_PEM_KEY_HEADER,
294                         strlen(PRIVATE_PEM_KEY_HEADER)) == 0) {
295                 key_type = PKT_PEM;
296                 begin = map + strlen(PRIVATE_PEM_KEY_HEADER);
297                 footer = strstr(map, PRIVATE_PEM_KEY_FOOTER);
298                 PARA_INFO_LOG("detected legacy PEM key %s\n", key_file);
299         } else if (strncmp(map, PRIVATE_OPENSSH_KEY_HEADER,
300                         strlen(PRIVATE_OPENSSH_KEY_HEADER)) == 0) {
301                 key_type = PKT_OPENSSH;
302                 begin = map + strlen(PRIVATE_OPENSSH_KEY_HEADER);
303                 footer = strstr(map, PRIVATE_OPENSSH_KEY_FOOTER);
304                 PARA_INFO_LOG("detected openssh key %s\n", key_file);
305         } else
306                 goto unmap;
307         if (!footer)
308                 goto unmap;
309         /* skip whitespace at the beginning */
310         for (; begin < footer; begin++) {
311                 if (para_isspace(*begin))
312                         continue;
313                 break;
314         }
315         ret = -E_KEY_MARKER;
316         if (begin >= footer)
317                 goto unmap;
318
319         key_size = footer - begin;
320         key = alloc(key_size + 1);
321         for (i = 0, j = 0; begin + i < footer; i++) {
322                 if (para_isspace(begin[i]))
323                         continue;
324                 key[j++] = begin[i];
325         }
326         key[j] = '\0';
327         ret = base64_decode(key, j, (char **)&blob, blob_size);
328         free(key);
329         if (ret < 0)
330                 goto unmap;
331         ret = key_type;
332 unmap:
333         ret2 = para_munmap(map, map_size);
334         if (ret >= 0 && ret2 < 0)
335                 ret = ret2;
336         if (ret < 0) {
337                 free(blob);
338                 blob = NULL;
339         }
340 out:
341         *result = blob;
342         return ret;
343 }
344