fixed some compiler warnings
[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, encryption_algorithm_t alg, size_t key_size,
93 chunk_t *iv, chunk_t *passphrase)
94 {
95 hasher_t *hasher;
96 crypter_t *crypter;
97 chunk_t hash;
98 chunk_t decrypted;
99 chunk_t key = {alloca(key_size), key_size};
100 u_int8_t padding, *last_padding_pos, *first_padding_pos;
101
102 if (passphrase == NULL || passphrase->len == 0)
103 return "missing passphrase";
104
105 /* build key from passphrase and IV */
106 hasher = hasher_create(HASH_MD5);
107 hash.len = hasher->get_hash_size(hasher);
108 hash.ptr = alloca(hash.len);
109 hasher->get_hash(hasher, *passphrase, NULL);
110 hasher->get_hash(hasher, *iv, hash.ptr);
111 memcpy(key.ptr, hash.ptr, hash.len);
112
113 printf("hash.len: %d, key.len: %d, iv.len: %d\n", hash.len, key.len, iv->len);
114 if (key.len > hash.len)
115 {
116 hasher->get_hash(hasher, hash, NULL);
117 hasher->get_hash(hasher, *passphrase, NULL);
118 hasher->get_hash(hasher, *iv, hash.ptr);
119 memcpy(key.ptr + hash.len, hash.ptr, key.len - hash.len);
120 }
121 hasher->destroy(hasher);
122
123 /* decrypt blob */
124 crypter = crypter_create(alg, key_size);
125 crypter->set_key(crypter, key);
126 logger->log_chunk(logger, CONTROL, " cipher text:", *blob);
127 if (crypter->decrypt(crypter, *blob, *iv, &decrypted) != SUCCESS)
128 {
129 return "data size is not multiple of block size";
130 }
131 memcpy(blob->ptr, decrypted.ptr, blob->len);
132 logger->log_chunk(logger, CONTROL, " plain text:", *blob);
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-256-CBC", &dek))
252 {
253 alg = ENCR_AES_CBC;
254 key_size = 32;
255 }
256 else
257 {
258 return "encryption algorithm not supported";
259 }
260
261 eat_whitespace(&value);
262 ugh = ttodata(value.ptr, value.len, 16, iv.ptr, 16, &len);
263 if (ugh)
264 return "error in IV";
265
266 iv.len = len;
267 }
268 }
269 else /* state is PEM_BODY */
270 {
271 const char *ugh = NULL;
272 size_t len = 0;
273 chunk_t data;
274
275 /* remove any trailing whitespace */
276 if (!extract_token(&data ,' ', &line))
277 {
278 data = line;
279 }
280
281 /* check for PGP armor checksum */
282 if (*data.ptr == '=')
283 {
284 *pgp = TRUE;
285 data.ptr++;
286 data.len--;
287 logger->log(logger, CONTROL|LEVEL2, " Armor checksum: %.*s",
288 (int)data.len, data.ptr);
289 continue;
290 }
291
292 ugh = ttodata(data.ptr, data.len, 64, dst.ptr, blob->len - dst.len, &len);
293 if (ugh)
294 {
295 state = PEM_ABORT;
296 break;
297 }
298 else
299 {
300 dst.ptr += len;
301 dst.len += len;
302 }
303 }
304 }
305 }
306 /* set length to size of binary blob */
307 blob->len = dst.len;
308
309 if (state != PEM_POST)
310 return "file coded in unknown format, discarded";
311
312 return (encrypted)? pem_decrypt(blob, alg, key_size, &iv, passphrase) : NULL;
313 }
314
315 /* load a coded key or certificate file with autodetection
316 * of binary DER or base64 PEM ASN.1 formats and armored PGP format
317 */
318 bool pem_asn1_load_file(const char *filename, chunk_t *passphrase,
319 const char *type, chunk_t *blob, bool *pgp)
320 {
321 err_t ugh = NULL;
322
323 FILE *fd = fopen(filename, "r");
324
325 pem_init_logger();
326
327 if (fd)
328 {
329 int bytes;
330 fseek(fd, 0, SEEK_END );
331 blob->len = ftell(fd);
332 rewind(fd);
333 blob->ptr = malloc(blob->len);
334 bytes = fread(blob->ptr, 1, blob->len, fd);
335 fclose(fd);
336 logger->log(logger, CONTROL, " loading %s file '%s' (%d bytes)", type, filename, bytes);
337
338 *pgp = FALSE;
339
340 /* try DER format */
341 if (is_asn1(*blob))
342 {
343 logger->log(logger, CONTROL|LEVEL1, " file coded in DER format");
344 return TRUE;
345 }
346
347 if (passphrase != NULL)
348 logger->log_bytes(logger, PRIVATE, " passphrase:", passphrase->ptr, passphrase->len);
349
350 /* try PEM format */
351 ugh = pem_to_bin(blob, passphrase, pgp);
352
353 if (ugh == NULL)
354 {
355 if (*pgp)
356 {
357 logger->log(logger, CONTROL|LEVEL1, " file coded in armored PGP format");
358 return TRUE;
359 }
360 if (is_asn1(*blob))
361 {
362 logger->log(logger, CONTROL|LEVEL1, " file coded in PEM format");
363 return TRUE;
364 }
365 ugh = "file coded in unknown format, discarded";
366 }
367
368 /* a conversion error has occured */
369 logger->log(logger, ERROR, " %s", ugh);
370 chunk_free(blob);
371 }
372 else
373 {
374 logger->log(logger, ERROR, " could not open %s file '%s'", type, filename);
375 }
376 return FALSE;
377 }