some cleanups here and there
[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 static logger_t *logger = NULL;
33
34 /**
35 * initializes the PEM logger
36 */
37 static void pem_init_logger(void)
38 {
39 if (logger == NULL)
40 logger = logger_manager->get_logger(logger_manager, ASN1);
41 }
42
43 /**
44 * check the presence of a pattern in a character string
45 */
46 static bool present(const char* pattern, chunk_t* ch)
47 {
48 u_int pattern_len = strlen(pattern);
49
50 if (ch->len >= pattern_len && strncmp(ch->ptr, pattern, pattern_len) == 0)
51 {
52 ch->ptr += pattern_len;
53 ch->len -= pattern_len;
54 return TRUE;
55 }
56 return FALSE;
57 }
58
59 /**
60 * find a boundary of the form -----tag name-----
61 */
62 static bool find_boundary(const char* tag, chunk_t *line)
63 {
64 chunk_t name = CHUNK_INITIALIZER;
65
66 if (!present("-----", line))
67 return FALSE;
68 if (!present(tag, line))
69 return FALSE;
70 if (*line->ptr != ' ')
71 return FALSE;
72 line->ptr++; line->len--;
73
74 /* extract name */
75 name.ptr = line->ptr;
76 while (line->len > 0)
77 {
78 if (present("-----", line))
79 {
80 logger->log(logger, CONTROL|LEVEL2,
81 " -----%s %.*s-----", tag, (int)name.len, name.ptr);
82 return TRUE;
83 }
84 line->ptr++; line->len--; name.len++;
85 }
86 return FALSE;
87 }
88
89 /*
90 * decrypts a DES-EDE-CBC encrypted data block
91 */
92 static err_t pem_decrypt(chunk_t *blob, chunk_t *iv, char *passphrase)
93 {
94 hasher_t *hasher;
95 crypter_t *crypter;
96 chunk_t hash;
97 chunk_t decrypted;
98 chunk_t pass = {(char*)passphrase, strlen(passphrase)};
99 chunk_t key = {alloca(24), 24};
100 u_int8_t padding, *last_padding_pos, *first_padding_pos;
101
102 /* build key from passphrase and IV */
103 hasher = hasher_create(HASH_MD5);
104 hash.len = hasher->get_hash_size(hasher);
105 hash.ptr = alloca(hash.len);
106 hasher->get_hash(hasher, pass, NULL);
107 hasher->get_hash(hasher, *iv, hash.ptr);
108
109 memcpy(key.ptr, hash.ptr, hash.len);
110
111 hasher->get_hash(hasher, hash, NULL);
112 hasher->get_hash(hasher, pass, NULL);
113 hasher->get_hash(hasher, *iv, hash.ptr);
114
115 memcpy(key.ptr + hash.len, hash.ptr, key.len - hash.len);
116
117 hasher->destroy(hasher);
118
119 /* decrypt blob */
120 crypter = crypter_create(ENCR_3DES, 0);
121 crypter->set_key(crypter, key);
122 crypter->decrypt(crypter, *blob, *iv, &decrypted);
123 memcpy(blob->ptr, decrypted.ptr, blob->len);
124 chunk_free(&decrypted);
125
126 /* determine amount of padding */
127 last_padding_pos = blob->ptr + blob->len - 1;
128 padding = *last_padding_pos;
129 first_padding_pos = (padding > blob->len) ? blob->ptr : last_padding_pos - padding;
130
131 /* check the padding pattern */
132 while (--last_padding_pos > first_padding_pos)
133 {
134 if (*last_padding_pos != padding)
135 return "invalid passphrase";
136 }
137 /* remove padding */
138 blob->len -= padding;
139 return NULL;
140 }
141
142 /* Converts a PEM encoded file into its binary form
143 *
144 * RFC 1421 Privacy Enhancement for Electronic Mail, February 1993
145 * RFC 934 Message Encapsulation, January 1985
146 */
147 err_t pem_to_bin(chunk_t *blob, char *passphrase, bool *pgp)
148 {
149 typedef enum {
150 PEM_PRE = 0,
151 PEM_MSG = 1,
152 PEM_HEADER = 2,
153 PEM_BODY = 3,
154 PEM_POST = 4,
155 PEM_ABORT = 5
156 } state_t;
157
158 bool encrypted = FALSE;
159
160 state_t state = PEM_PRE;
161
162 chunk_t src = *blob;
163 chunk_t dst = *blob;
164 chunk_t line = CHUNK_INITIALIZER;
165 chunk_t iv = CHUNK_INITIALIZER;
166
167 u_char iv_buf[16]; /* MD5 digest size */
168
169 /* zero size of converted blob */
170 dst.len = 0;
171
172 /* zero size of IV */
173 iv.ptr = iv_buf;
174 iv.len = 0;
175
176 pem_init_logger();
177
178 while (fetchline(&src, &line))
179 {
180 if (state == PEM_PRE)
181 {
182 if (find_boundary("BEGIN", &line))
183 {
184 state = PEM_MSG;
185 }
186 continue;
187 }
188 else
189 {
190 if (find_boundary("END", &line))
191 {
192 state = PEM_POST;
193 break;
194 }
195 if (state == PEM_MSG)
196 {
197 state = (memchr(line.ptr, ':', line.len) == NULL) ? PEM_BODY : PEM_HEADER;
198 }
199 if (state == PEM_HEADER)
200 {
201 chunk_t name = CHUNK_INITIALIZER;
202 chunk_t value = CHUNK_INITIALIZER;
203
204 /* an empty line separates HEADER and BODY */
205 if (line.len == 0)
206 {
207 state = PEM_BODY;
208 continue;
209 }
210
211 /* we are looking for a parameter: value pair */
212 logger->log(logger, CONTROL|LEVEL2, " %.*s", (int)line.len, line.ptr);
213 if (!extract_parameter_value(&name, &value, &line))
214 continue;
215
216 if (match("Proc-Type", &name) && *value.ptr == '4')
217 encrypted = TRUE;
218 else if (match("DEK-Info", &name))
219 {
220 const char *ugh = NULL;
221 size_t len = 0;
222 chunk_t dek;
223
224 if (!extract_token(&dek, ',', &value))
225 dek = value;
226
227 /* we support DES-EDE3-CBC encrypted files, only */
228 if (!match("DES-EDE3-CBC", &dek))
229 return "encryption algorithm not supported";
230
231 eat_whitespace(&value);
232 ugh = ttodata(value.ptr, value.len, 16, iv.ptr, 16, &len);
233 if (ugh)
234 return "error in IV";
235
236 iv.len = len;
237 }
238 }
239 else /* state is PEM_BODY */
240 {
241 const char *ugh = NULL;
242 size_t len = 0;
243 chunk_t data;
244
245 /* remove any trailing whitespace */
246 if (!extract_token(&data ,' ', &line))
247 {
248 data = line;
249 }
250
251 /* check for PGP armor checksum */
252 if (*data.ptr == '=')
253 {
254 *pgp = TRUE;
255 data.ptr++;
256 data.len--;
257 logger->log(logger, CONTROL|LEVEL2, " Armor checksum: %.*s",
258 (int)data.len, data.ptr);
259 continue;
260 }
261
262 ugh = ttodata(data.ptr, data.len, 64, dst.ptr, blob->len - dst.len, &len);
263 if (ugh)
264 {
265 state = PEM_ABORT;
266 break;
267 }
268 else
269 {
270 dst.ptr += len;
271 dst.len += len;
272 }
273 }
274 }
275 }
276 /* set length to size of binary blob */
277 blob->len = dst.len;
278
279 if (state != PEM_POST)
280 return "file coded in unknown format, discarded";
281
282 return (encrypted)? pem_decrypt(blob, &iv, passphrase) : NULL;
283 }
284
285 /* load a coded key or certificate file with autodetection
286 * of binary DER or base64 PEM ASN.1 formats and armored PGP format
287 */
288 bool pem_asn1_load_file(const char *filename, char *passphrase,
289 const char *type, chunk_t *blob, bool *pgp)
290 {
291 err_t ugh = NULL;
292
293 FILE *fd = fopen(filename, "r");
294
295 pem_init_logger();
296
297 if (fd)
298 {
299 int bytes;
300 fseek(fd, 0, SEEK_END );
301 blob->len = ftell(fd);
302 rewind(fd);
303 blob->ptr = malloc(blob->len);
304 bytes = fread(blob->ptr, 1, blob->len, fd);
305 fclose(fd);
306 logger->log(logger, CONTROL, " loading %s file '%s' (%d bytes)", type, filename, bytes);
307
308 *pgp = FALSE;
309
310 /* try DER format */
311 if (is_asn1(*blob))
312 {
313 logger->log(logger, CONTROL|LEVEL1, " file coded in DER format");
314 return TRUE;
315 }
316
317 /* try PEM format */
318 ugh = pem_to_bin(blob, passphrase, pgp);
319
320 if (ugh == NULL)
321 {
322 if (*pgp)
323 {
324 logger->log(logger, CONTROL|LEVEL1, " file coded in armored PGP format");
325 return TRUE;
326 }
327 if (is_asn1(*blob))
328 {
329 logger->log(logger, CONTROL|LEVEL1, " file coded in PEM format");
330 return TRUE;
331 }
332 ugh = "file coded in unknown format, discarded";
333 }
334
335 /* a conversion error has occured */
336 logger->log(logger, ERROR, " %s", ugh);
337 chunk_free(blob);
338 }
339 else
340 {
341 logger->log(logger, ERROR, " could not open %s file '%s'", type, filename);
342 }
343 return FALSE;
344 }