e07b9fe434974afed45b1abcdda1a16f3df58b84
[strongswan.git] / src / libstrongswan / asn1 / pem.c
1 /*
2 * Copyright (C) 2001-2008 Andreas Steffen
3 *
4 * Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 *
16 * $Id$
17 */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <stddef.h>
25 #include <sys/types.h>
26
27 #include "pem.h"
28
29 #include <library.h>
30 #include <debug.h>
31 #include <asn1/asn1.h>
32
33 #include <utils/lexparser.h>
34 #include <crypto/hashers/hasher.h>
35 #include <crypto/crypters/crypter.h>
36
37 #define PKCS5_SALT_LEN 8 /* bytes */
38
39 /**
40 * check the presence of a pattern in a character string
41 */
42 static bool present(const char* pattern, chunk_t* ch)
43 {
44 u_int pattern_len = strlen(pattern);
45
46 if (ch->len >= pattern_len && strneq(ch->ptr, pattern, pattern_len))
47 {
48 ch->ptr += pattern_len;
49 ch->len -= pattern_len;
50 return TRUE;
51 }
52 return FALSE;
53 }
54
55 /**
56 * find a boundary of the form -----tag name-----
57 */
58 static bool find_boundary(const char* tag, chunk_t *line)
59 {
60 chunk_t name = chunk_empty;
61
62 if (!present("-----", line))
63 return FALSE;
64 if (!present(tag, line))
65 return FALSE;
66 if (*line->ptr != ' ')
67 return FALSE;
68 line->ptr++; line->len--;
69
70 /* extract name */
71 name.ptr = line->ptr;
72 while (line->len > 0)
73 {
74 if (present("-----", line))
75 {
76 DBG2(" -----%s %.*s-----", tag, (int)name.len, name.ptr);
77 return TRUE;
78 }
79 line->ptr++; line->len--; name.len++;
80 }
81 return FALSE;
82 }
83
84 /*
85 * decrypts a passphrase protected encrypted data block
86 */
87 static bool pem_decrypt(chunk_t *blob, encryption_algorithm_t alg, size_t key_size,
88 chunk_t *iv, chunk_t *passphrase)
89 {
90 hasher_t *hasher;
91 crypter_t *crypter;
92 chunk_t salt = { iv->ptr, PKCS5_SALT_LEN };
93 chunk_t hash;
94 chunk_t decrypted;
95 chunk_t key = {alloca(key_size), key_size};
96 u_int8_t padding, *last_padding_pos, *first_padding_pos;
97
98 if (passphrase == NULL || passphrase->len == 0)
99 {
100 DBG1(" missing passphrase");
101 return FALSE;
102 }
103
104 /* build key from passphrase and IV */
105 hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
106 if (hasher == NULL)
107 {
108 DBG1(" MD5 hash algorithm not available");
109 return FALSE;
110 }
111 hash.len = hasher->get_hash_size(hasher);
112 hash.ptr = alloca(hash.len);
113 hasher->get_hash(hasher, *passphrase, NULL);
114 hasher->get_hash(hasher, salt, hash.ptr);
115 memcpy(key.ptr, hash.ptr, hash.len);
116
117 if (key.len > hash.len)
118 {
119 hasher->get_hash(hasher, hash, NULL);
120 hasher->get_hash(hasher, *passphrase, NULL);
121 hasher->get_hash(hasher, salt, hash.ptr);
122 memcpy(key.ptr + hash.len, hash.ptr, key.len - hash.len);
123 }
124 hasher->destroy(hasher);
125
126 /* decrypt blob */
127 crypter = lib->crypto->create_crypter(lib->crypto, alg, key_size);
128 if (crypter == NULL)
129 {
130 DBG1(" %N encryption algorithm not available",
131 encryption_algorithm_names, alg);
132 return FALSE;
133 }
134 crypter->set_key(crypter, key);
135
136 if (iv->len != crypter->get_block_size(crypter) ||
137 blob->len % iv->len)
138 {
139 crypter->destroy(crypter);
140 DBG1(" data size is not multiple of block size");
141 return FALSE;
142 }
143 crypter->decrypt(crypter, *blob, *iv, &decrypted);
144 crypter->destroy(crypter);
145 memcpy(blob->ptr, decrypted.ptr, blob->len);
146 chunk_free(&decrypted);
147
148 /* determine amount of padding */
149 last_padding_pos = blob->ptr + blob->len - 1;
150 padding = *last_padding_pos;
151 first_padding_pos = (padding > blob->len) ? blob->ptr : last_padding_pos - padding;
152
153 /* check the padding pattern */
154 while (--last_padding_pos > first_padding_pos)
155 {
156 if (*last_padding_pos != padding)
157 {
158 DBG1(" invalid passphrase");
159 return FALSE;
160 }
161 }
162 /* remove padding */
163 blob->len -= padding;
164 return TRUE;
165 }
166
167 /* Converts a PEM encoded file into its binary form
168 *
169 * RFC 1421 Privacy Enhancement for Electronic Mail, February 1993
170 * RFC 934 Message Encapsulation, January 1985
171 */
172 bool pem_to_bin(chunk_t *blob, chunk_t *passphrase, bool *pgp)
173 {
174 typedef enum {
175 PEM_PRE = 0,
176 PEM_MSG = 1,
177 PEM_HEADER = 2,
178 PEM_BODY = 3,
179 PEM_POST = 4,
180 PEM_ABORT = 5
181 } state_t;
182
183 encryption_algorithm_t alg = ENCR_UNDEFINED;
184 size_t key_size = 0;
185
186 bool encrypted = FALSE;
187
188 state_t state = PEM_PRE;
189
190 chunk_t src = *blob;
191 chunk_t dst = *blob;
192 chunk_t line = chunk_empty;
193 chunk_t iv = chunk_empty;
194
195 u_char iv_buf[16]; /* MD5 digest size */
196
197 /* zero size of converted blob */
198 dst.len = 0;
199
200 /* zero size of IV */
201 iv.ptr = iv_buf;
202 iv.len = 0;
203
204 while (fetchline(&src, &line))
205 {
206 if (state == PEM_PRE)
207 {
208 if (find_boundary("BEGIN", &line))
209 {
210 state = PEM_MSG;
211 }
212 continue;
213 }
214 else
215 {
216 if (find_boundary("END", &line))
217 {
218 state = PEM_POST;
219 break;
220 }
221 if (state == PEM_MSG)
222 {
223 state = (memchr(line.ptr, ':', line.len) == NULL) ? PEM_BODY : PEM_HEADER;
224 }
225 if (state == PEM_HEADER)
226 {
227 err_t ugh = NULL;
228 chunk_t name = chunk_empty;
229 chunk_t value = chunk_empty;
230
231 /* an empty line separates HEADER and BODY */
232 if (line.len == 0)
233 {
234 state = PEM_BODY;
235 continue;
236 }
237
238 /* we are looking for a parameter: value pair */
239 DBG2(" %.*s", (int)line.len, line.ptr);
240 ugh = extract_parameter_value(&name, &value, &line);
241 if (ugh != NULL)
242 continue;
243
244 if (match("Proc-Type", &name) && *value.ptr == '4')
245 encrypted = TRUE;
246 else if (match("DEK-Info", &name))
247 {
248 chunk_t dek;
249
250 if (!extract_token(&dek, ',', &value))
251 dek = value;
252
253 if (match("DES-EDE3-CBC", &dek))
254 {
255 alg = ENCR_3DES;
256 key_size = 24;
257 }
258 else if (match("AES-128-CBC", &dek))
259 {
260 alg = ENCR_AES_CBC;
261 key_size = 16;
262 }
263 else if (match("AES-192-CBC", &dek))
264 {
265 alg = ENCR_AES_CBC;
266 key_size = 24;
267 }
268 else if (match("AES-256-CBC", &dek))
269 {
270 alg = ENCR_AES_CBC;
271 key_size = 32;
272 }
273 else
274 {
275 DBG1(" encryption algorithm '%.s' not supported",
276 dek.len, dek.ptr);
277 return FALSE;
278 }
279 eat_whitespace(&value);
280 iv = chunk_from_hex(value, iv.ptr);
281 }
282 }
283 else /* state is PEM_BODY */
284 {
285 chunk_t data;
286
287 /* remove any trailing whitespace */
288 if (!extract_token(&data ,' ', &line))
289 {
290 data = line;
291 }
292
293 /* check for PGP armor checksum */
294 if (*data.ptr == '=')
295 {
296 *pgp = TRUE;
297 data.ptr++;
298 data.len--;
299 DBG2(" armor checksum: %.*s", (int)data.len, data.ptr);
300 continue;
301 }
302
303 if (blob->len - dst.len < data.len / 4 * 3)
304 {
305 state = PEM_ABORT;
306 }
307 data = chunk_from_base64(data, dst.ptr);
308
309 dst.ptr += data.len;
310 dst.len += data.len;
311 }
312 }
313 }
314 /* set length to size of binary blob */
315 blob->len = dst.len;
316
317 if (state != PEM_POST)
318 {
319 DBG1(" file coded in unknown format, discarded");
320 return FALSE;
321 }
322 if (!encrypted)
323 {
324 return TRUE;
325 }
326 return pem_decrypt(blob, alg, key_size, &iv, passphrase);
327
328 }
329
330 /* load a coded key or certificate file with autodetection
331 * of binary DER or base64 PEM ASN.1 formats and armored PGP format
332 */
333 bool pem_asn1_load_file(char *filename, chunk_t *passphrase,
334 chunk_t *blob, bool *pgp)
335 {
336 err_t ugh = NULL;
337
338 FILE *fd = fopen(filename, "r");
339
340 if (fd)
341 {
342 int bytes;
343 fseek(fd, 0, SEEK_END );
344 blob->len = ftell(fd);
345 rewind(fd);
346 blob->ptr = malloc(blob->len);
347 bytes = fread(blob->ptr, 1, blob->len, fd);
348 fclose(fd);
349 DBG2(" loading '%s' (%d bytes)", filename, bytes);
350
351 *pgp = FALSE;
352
353 /* try DER format */
354 if (is_asn1(*blob))
355 {
356 DBG2(" file coded in DER format");
357 return TRUE;
358 }
359
360 if (passphrase != NULL)
361 DBG4(" passphrase:", passphrase->ptr, passphrase->len);
362
363 /* try PEM format */
364 if (pem_to_bin(blob, passphrase, pgp))
365 {
366 if (*pgp)
367 {
368 DBG2(" file coded in armored PGP format");
369 return TRUE;
370 }
371 if (is_asn1(*blob))
372 {
373 DBG2(" file coded in PEM format");
374 return TRUE;
375 }
376 DBG1(" file coded in unknown format, discarded");
377 }
378
379 /* a conversion error has occured */
380 chunk_free(blob);
381 }
382 else
383 {
384 DBG1(" reading file '%s' failed", filename);
385 }
386 return FALSE;
387 }