vss: Improve error diagnostics.
[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(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(unsigned char *h1, 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 /**
164 * Check header of an openssh private key and compute bignum offset.
165 *
166 * \param data The base64-decoded key.
167 * \param len The size of the decoded key.
168 *
169 * Several assumptions are made about the key. Most notably, we only support
170 * single unencrypted keys without comments.
171 *
172 * \return The offset at which the first bignum of the private key (the public
173 * exponent n) starts. Negative error code on failure.
174 */
175 int find_openssh_bignum_offset(const unsigned char *data, int len)
176 {
177 /*
178 * Unencrypted keys without comments always start with the below byte
179 * sequence. See PROTOCOL.key of the openssh package.
180 */
181 static const unsigned char valid_openssh_header[] = {
182 /* string "openssh-key-v1" */
183 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2d, 0x6b, 0x65,
184 0x79, 0x2d, 0x76, 0x31,
185 /* length of the cipher name */
186 0x00, 0x00, 0x00, 0x00, 0x04,
187 /* cipher name: "none" */
188 0x6e, 0x6f, 0x6e, 0x65,
189 /* length of the kdfname (only used for encrypted keys) */
190 0x00, 0x00, 0x00, 0x04,
191 /* kdfname: "none" */
192 0x6e, 0x6f, 0x6e, 0x65,
193 /* length of kdfoptions */
194 0x00, 0x00, 0x00, 0x00,
195 /* number of keys */
196 0x00, 0x00, 0x00, 0x01,
197 };
198 uint32_t val;
199 const unsigned char *p, *end = data + len;
200
201 if (len <= sizeof(valid_openssh_header) + 4)
202 return -E_OPENSSH_PARSE;
203 if (memcmp(data, valid_openssh_header, sizeof(valid_openssh_header)))
204 return -E_OPENSSH_PARSE;
205 p = data + sizeof(valid_openssh_header);
206 /* length of public key */
207 val = read_u32_be(p);
208 if (val > end - p - 4)
209 return -E_OPENSSH_PARSE;
210 p += val + 4;
211 /* length of private key */
212 val = read_u32_be(p);
213 if (val > end - p - 4)
214 return -E_OPENSSH_PARSE;
215 p += 4;
216 /* two equal random integers ("checkint") */
217 if (p + 8 > end)
218 return -E_OPENSSH_PARSE;
219 if (read_u32_be(p) != read_u32_be(p + 4))
220 return -E_OPENSSH_PARSE;
221 p += 8;
222 /* length of name of key type "ssh-rsa" */
223 if (p + 11 > end)
224 return -E_OPENSSH_PARSE;
225 if (read_u32_be(p) != 7)
226 return -E_OPENSSH_PARSE;
227 if (memcmp(p + 4, "ssh-rsa", 7))
228 return -E_OPENSSH_PARSE;
229 p += 11;
230 return p - data;
231 }
232
233 /** Private PEM keys (legacy format) start with this header. */
234 #define PRIVATE_PEM_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
235 /** Private OPENSSH keys (RFC4716) start with this header. */
236 #define PRIVATE_OPENSSH_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----"
237 /** Private PEM keys (legacy format) end with this footer. */
238 #define PRIVATE_PEM_KEY_FOOTER "-----END RSA PRIVATE KEY-----"
239 /** Private OPENSSH keys (RFC4716) end with this footer. */
240 #define PRIVATE_OPENSSH_KEY_FOOTER "-----END OPENSSH PRIVATE KEY-----"
241
242 /**
243 * Decode an openssh-v1 (aka RFC4716) or PEM (aka ASN.1) private key.
244 *
245 * \param key_file The private key file (usually id_rsa).
246 * \param result Pointer to base64-decoded blob is returned here.
247 * \param blob_size The size of the decoded blob.
248 *
249 * This only checks header and footer and base64-decodes the part in between.
250 * No attempt to read the decoded part is made.
251 *
252 * \return Negative on errors, PKT_PEM or PKT_OPENSSH on success, indicating
253 * the type of key.
254 */
255 int decode_private_key(const char *key_file, unsigned char **result,
256 size_t *blob_size)
257 {
258 int ret, ret2, i, j, key_type;
259 void *map;
260 size_t map_size, key_size;
261 unsigned char *blob = NULL;
262 char *begin, *footer, *key;
263
264 ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
265 if (ret < 0)
266 goto out;
267 ret = -E_KEY_MARKER;
268 if (strncmp(map, PRIVATE_PEM_KEY_HEADER,
269 strlen(PRIVATE_PEM_KEY_HEADER)) == 0) {
270 key_type = PKT_PEM;
271 begin = map + strlen(PRIVATE_PEM_KEY_HEADER);
272 footer = strstr(map, PRIVATE_PEM_KEY_FOOTER);
273 PARA_INFO_LOG("detected legacy PEM key %s\n", key_file);
274 } else if (strncmp(map, PRIVATE_OPENSSH_KEY_HEADER,
275 strlen(PRIVATE_OPENSSH_KEY_HEADER)) == 0) {
276 key_type = PKT_OPENSSH;
277 begin = map + strlen(PRIVATE_OPENSSH_KEY_HEADER);
278 footer = strstr(map, PRIVATE_OPENSSH_KEY_FOOTER);
279 PARA_INFO_LOG("detected openssh key %s\n", key_file);
280 } else
281 goto unmap;
282 if (!footer)
283 goto unmap;
284 /* skip whitespace at the beginning */
285 for (; begin < footer; begin++) {
286 if (para_isspace(*begin))
287 continue;
288 break;
289 }
290 ret = -E_KEY_MARKER;
291 if (begin >= footer)
292 goto unmap;
293
294 key_size = footer - begin;
295 key = para_malloc(key_size + 1);
296 for (i = 0, j = 0; begin + i < footer; i++) {
297 if (para_isspace(begin[i]))
298 continue;
299 key[j++] = begin[i];
300 }
301 key[j] = '\0';
302 ret = base64_decode(key, j, (char **)&blob, blob_size);
303 free(key);
304 if (ret < 0)
305 goto unmap;
306 ret = key_type;
307 unmap:
308 ret2 = para_munmap(map, map_size);
309 if (ret >= 0 && ret2 < 0)
310 ret = ret2;
311 if (ret < 0) {
312 free(blob);
313 blob = NULL;
314 }
315 out:
316 *result = blob;
317 return ret;
318 }
319