c79e98b94f377c47577ba217679beb2c1dcc323b
[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 err_t 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 return "missing passphrase";
101 }
102
103 /* build key from passphrase and IV */
104 hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
105 if (hasher == NULL)
106 {
107 return "MD5 hasher not supported";
108 }
109 hash.len = hasher->get_hash_size(hasher);
110 hash.ptr = alloca(hash.len);
111 hasher->get_hash(hasher, *passphrase, NULL);
112 hasher->get_hash(hasher, salt, hash.ptr);
113 memcpy(key.ptr, hash.ptr, hash.len);
114
115 if (key.len > hash.len)
116 {
117 hasher->get_hash(hasher, hash, NULL);
118 hasher->get_hash(hasher, *passphrase, NULL);
119 hasher->get_hash(hasher, salt, hash.ptr);
120 memcpy(key.ptr + hash.len, hash.ptr, key.len - hash.len);
121 }
122 hasher->destroy(hasher);
123
124 /* decrypt blob */
125 crypter = lib->crypto->create_crypter(lib->crypto, alg, key_size);
126 crypter->set_key(crypter, key);
127
128 if (iv->len != crypter->get_block_size(crypter) ||
129 blob->len % iv->len)
130 {
131 crypter->destroy(crypter);
132 return "data size is not multiple of block size";
133 }
134 crypter->decrypt(crypter, *blob, *iv, &decrypted);
135 crypter->destroy(crypter);
136 memcpy(blob->ptr, decrypted.ptr, blob->len);
137 chunk_free(&decrypted);
138
139 /* determine amount of padding */
140 last_padding_pos = blob->ptr + blob->len - 1;
141 padding = *last_padding_pos;
142 first_padding_pos = (padding > blob->len) ? blob->ptr : last_padding_pos - padding;
143
144 /* check the padding pattern */
145 while (--last_padding_pos > first_padding_pos)
146 {
147 if (*last_padding_pos != padding)
148 return "invalid passphrase";
149 }
150 /* remove padding */
151 blob->len -= padding;
152 return NULL;
153 }
154
155 /* Converts a PEM encoded file into its binary form
156 *
157 * RFC 1421 Privacy Enhancement for Electronic Mail, February 1993
158 * RFC 934 Message Encapsulation, January 1985
159 */
160 err_t pem_to_bin(chunk_t *blob, chunk_t *passphrase, bool *pgp)
161 {
162 typedef enum {
163 PEM_PRE = 0,
164 PEM_MSG = 1,
165 PEM_HEADER = 2,
166 PEM_BODY = 3,
167 PEM_POST = 4,
168 PEM_ABORT = 5
169 } state_t;
170
171 encryption_algorithm_t alg = ENCR_UNDEFINED;
172 size_t key_size = 0;
173
174 bool encrypted = FALSE;
175
176 state_t state = PEM_PRE;
177
178 chunk_t src = *blob;
179 chunk_t dst = *blob;
180 chunk_t line = chunk_empty;
181 chunk_t iv = chunk_empty;
182
183 u_char iv_buf[16]; /* MD5 digest size */
184
185 /* zero size of converted blob */
186 dst.len = 0;
187
188 /* zero size of IV */
189 iv.ptr = iv_buf;
190 iv.len = 0;
191
192 while (fetchline(&src, &line))
193 {
194 if (state == PEM_PRE)
195 {
196 if (find_boundary("BEGIN", &line))
197 {
198 state = PEM_MSG;
199 }
200 continue;
201 }
202 else
203 {
204 if (find_boundary("END", &line))
205 {
206 state = PEM_POST;
207 break;
208 }
209 if (state == PEM_MSG)
210 {
211 state = (memchr(line.ptr, ':', line.len) == NULL) ? PEM_BODY : PEM_HEADER;
212 }
213 if (state == PEM_HEADER)
214 {
215 err_t ugh = NULL;
216 chunk_t name = chunk_empty;
217 chunk_t value = chunk_empty;
218
219 /* an empty line separates HEADER and BODY */
220 if (line.len == 0)
221 {
222 state = PEM_BODY;
223 continue;
224 }
225
226 /* we are looking for a parameter: value pair */
227 DBG2(" %.*s", (int)line.len, line.ptr);
228 ugh = extract_parameter_value(&name, &value, &line);
229 if (ugh != NULL)
230 continue;
231
232 if (match("Proc-Type", &name) && *value.ptr == '4')
233 encrypted = TRUE;
234 else if (match("DEK-Info", &name))
235 {
236 chunk_t dek;
237
238 if (!extract_token(&dek, ',', &value))
239 dek = value;
240
241 if (match("DES-EDE3-CBC", &dek))
242 {
243 alg = ENCR_3DES;
244 key_size = 24;
245 }
246 else if (match("AES-128-CBC", &dek))
247 {
248 alg = ENCR_AES_CBC;
249 key_size = 16;
250 }
251 else if (match("AES-192-CBC", &dek))
252 {
253 alg = ENCR_AES_CBC;
254 key_size = 24;
255 }
256 else if (match("AES-256-CBC", &dek))
257 {
258 alg = ENCR_AES_CBC;
259 key_size = 32;
260 }
261 else
262 {
263 return "encryption algorithm not supported";
264 }
265 eat_whitespace(&value);
266 iv = chunk_from_hex(value, iv.ptr);
267 }
268 }
269 else /* state is PEM_BODY */
270 {
271 chunk_t data;
272
273 /* remove any trailing whitespace */
274 if (!extract_token(&data ,' ', &line))
275 {
276 data = line;
277 }
278
279 /* check for PGP armor checksum */
280 if (*data.ptr == '=')
281 {
282 *pgp = TRUE;
283 data.ptr++;
284 data.len--;
285 DBG2(" Armor checksum: %.*s", (int)data.len, data.ptr);
286 continue;
287 }
288
289 if (blob->len - dst.len < data.len / 4 * 3)
290 {
291 state = PEM_ABORT;
292 }
293 data = chunk_from_base64(data, dst.ptr);
294
295 dst.ptr += data.len;
296 dst.len += data.len;
297 }
298 }
299 }
300 /* set length to size of binary blob */
301 blob->len = dst.len;
302
303 if (state != PEM_POST)
304 return "file coded in unknown format, discarded";
305
306 if (!encrypted)
307 {
308 return NULL;
309 }
310 return pem_decrypt(blob, alg, key_size, &iv, passphrase);
311
312 }
313
314 /* load a coded key or certificate file with autodetection
315 * of binary DER or base64 PEM ASN.1 formats and armored PGP format
316 */
317 bool pem_asn1_load_file(char *filename, chunk_t *passphrase,
318 chunk_t *blob, bool *pgp)
319 {
320 err_t ugh = NULL;
321
322 FILE *fd = fopen(filename, "r");
323
324 if (fd)
325 {
326 int bytes;
327 fseek(fd, 0, SEEK_END );
328 blob->len = ftell(fd);
329 rewind(fd);
330 blob->ptr = malloc(blob->len);
331 bytes = fread(blob->ptr, 1, blob->len, fd);
332 fclose(fd);
333 DBG2(" loading '%s' (%d bytes)", filename, bytes);
334
335 *pgp = FALSE;
336
337 /* try DER format */
338 if (is_asn1(*blob))
339 {
340 DBG2(" file coded in DER format");
341 return TRUE;
342 }
343
344 if (passphrase != NULL)
345 DBG4(" passphrase:", passphrase->ptr, passphrase->len);
346
347 /* try PEM format */
348 ugh = pem_to_bin(blob, passphrase, pgp);
349
350 if (ugh == NULL)
351 {
352 if (*pgp)
353 {
354 DBG2(" file coded in armored PGP format");
355 return TRUE;
356 }
357 if (is_asn1(*blob))
358 {
359 DBG2(" file coded in PEM format");
360 return TRUE;
361 }
362 ugh = "file coded in unknown format, discarded";
363 }
364
365 /* a conversion error has occured */
366 DBG1(" %s", ugh);
367 chunk_free(blob);
368 }
369 else
370 {
371 DBG1(" reading file '%s' failed", filename);
372 }
373 return FALSE;
374 }