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