handle zero size Base64 conversions
[strongswan.git] / src / scepclient / scep.c
1 /**
2 * @file scep.c
3 * @brief SCEP specific functions
4 *
5 * Contains functions to build SCEP request's and to parse SCEP reply's.
6 */
7
8 /*
9 * Copyright (C) 2005 Jan Hutter, Martin Willi
10 * Hochschule fuer Technik Rapperswil
11 *
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the
14 * Free Software Foundation; either version 2 of the License, or (at your
15 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 * for more details.
21 */
22
23 #include <string.h>
24 #include <stdlib.h>
25
26 #include <freeswan.h>
27
28 #include <library.h>
29 #include <asn1/asn1.h>
30 #include <asn1/asn1_parser.h>
31 #include <asn1/oid.h>
32 #include <crypto/rngs/rng.h>
33 #include <crypto/hashers/hasher.h>
34
35 #include "../pluto/constants.h"
36 #include "../pluto/defs.h"
37 #include "../pluto/fetch.h"
38 #include "../pluto/log.h"
39
40 #include "scep.h"
41
42 static const chunk_t ASN1_messageType_oid = chunk_from_chars(
43 0x06, 0x0A, 0x60, 0x86, 0x48, 0x01, 0x86, 0xF8, 0x45, 0x01, 0x09, 0x02
44 );
45 static const chunk_t ASN1_senderNonce_oid = chunk_from_chars(
46 0x06, 0x0A, 0x60, 0x86, 0x48, 0x01, 0x86, 0xF8, 0x45, 0x01, 0x09, 0x05
47 );
48 static const chunk_t ASN1_transId_oid = chunk_from_chars(
49 0x06, 0x0A, 0x60, 0x86, 0x48, 0x01, 0x86, 0xF8, 0x45, 0x01, 0x09, 0x07
50 );
51
52 static const char *pkiStatus_values[] = { "0", "2", "3" };
53
54 static const char *pkiStatus_names[] = {
55 "SUCCESS",
56 "FAILURE",
57 "PENDING",
58 "UNKNOWN"
59 };
60
61 static const char *msgType_values[] = { "3", "19", "20", "21", "22" };
62
63 static const char *msgType_names[] = {
64 "CertRep",
65 "PKCSReq",
66 "GetCertInitial",
67 "GetCert",
68 "GetCRL",
69 "Unknown"
70 };
71
72 static const char *failInfo_reasons[] = {
73 "badAlg - unrecognized or unsupported algorithm identifier",
74 "badMessageCheck - integrity check failed",
75 "badRequest - transaction not permitted or supported",
76 "badTime - Message time field was not sufficiently close to the system time",
77 "badCertId - No certificate could be identified matching the provided criteria"
78 };
79
80 const scep_attributes_t empty_scep_attributes = {
81 SCEP_Unknown_MSG , /* msgType */
82 SCEP_UNKNOWN , /* pkiStatus */
83 SCEP_unknown_REASON, /* failInfo */
84 { NULL, 0 } , /* transID */
85 { NULL, 0 } , /* senderNonce */
86 { NULL, 0 } , /* recipientNonce */
87 };
88
89 /* ASN.1 definition of the X.501 atttribute type */
90
91 static const asn1Object_t attributesObjects[] = {
92 { 0, "attributes", ASN1_SET, ASN1_LOOP }, /* 0 */
93 { 1, "attribute", ASN1_SEQUENCE, ASN1_NONE }, /* 1 */
94 { 2, "type", ASN1_OID, ASN1_BODY }, /* 2 */
95 { 2, "values", ASN1_SET, ASN1_LOOP }, /* 3 */
96 { 3, "value", ASN1_EOC, ASN1_RAW }, /* 4 */
97 { 2, "end loop", ASN1_EOC, ASN1_END }, /* 5 */
98 { 0, "end loop", ASN1_EOC, ASN1_END }, /* 6 */
99 { 0, "exit", ASN1_EOC, ASN1_EXIT }
100 };
101 #define ATTRIBUTE_OBJ_TYPE 2
102 #define ATTRIBUTE_OBJ_VALUE 4
103
104 /**
105 * Extract and store an attribute
106 */
107 static bool extract_attribute(int oid, chunk_t object, u_int level,
108 scep_attributes_t *attrs)
109 {
110 asn1_t type = ASN1_EOC;
111 const char *name = "none";
112
113 switch (oid)
114 {
115 case OID_PKCS9_CONTENT_TYPE:
116 type = ASN1_OID;
117 name = "contentType";
118 break;
119 case OID_PKCS9_SIGNING_TIME:
120 type = ASN1_UTCTIME;
121 name = "signingTime";
122 break;
123 case OID_PKCS9_MESSAGE_DIGEST:
124 type = ASN1_OCTET_STRING;
125 name = "messageDigest";
126 break;
127 case OID_PKI_MESSAGE_TYPE:
128 type = ASN1_PRINTABLESTRING;
129 name = "messageType";
130 break;
131 case OID_PKI_STATUS:
132 type = ASN1_PRINTABLESTRING;
133 name = "pkiStatus";
134 break;
135 case OID_PKI_FAIL_INFO:
136 type = ASN1_PRINTABLESTRING;
137 name = "failInfo";
138 break;
139 case OID_PKI_SENDER_NONCE:
140 type = ASN1_OCTET_STRING;
141 name = "senderNonce";
142 break;
143 case OID_PKI_RECIPIENT_NONCE:
144 type = ASN1_OCTET_STRING;
145 name = "recipientNonce";
146 break;
147 case OID_PKI_TRANS_ID:
148 type = ASN1_PRINTABLESTRING;
149 name = "transID";
150 break;
151 default:
152 break;
153 }
154
155 if (type == ASN1_EOC)
156 return TRUE;
157
158 if (!asn1_parse_simple_object(&object, type, level+1, name))
159 return FALSE;
160
161 switch (oid)
162 {
163 case OID_PKCS9_CONTENT_TYPE:
164 break;
165 case OID_PKCS9_SIGNING_TIME:
166 break;
167 case OID_PKCS9_MESSAGE_DIGEST:
168 break;
169 case OID_PKI_MESSAGE_TYPE:
170 {
171 scep_msg_t m;
172
173 for (m = SCEP_CertRep_MSG; m < SCEP_Unknown_MSG; m++)
174 {
175 if (strncmp(msgType_values[m], object.ptr, object.len) == 0)
176 attrs->msgType = m;
177 }
178 DBG(DBG_CONTROL,
179 DBG_log("messageType: %s", msgType_names[attrs->msgType])
180 )
181 }
182 break;
183 case OID_PKI_STATUS:
184 {
185 pkiStatus_t s;
186
187 for (s = SCEP_SUCCESS; s < SCEP_UNKNOWN; s++)
188 {
189 if (strncmp(pkiStatus_values[s], object.ptr, object.len) == 0)
190 attrs->pkiStatus = s;
191 }
192 DBG(DBG_CONTROL,
193 DBG_log("pkiStatus: %s", pkiStatus_names[attrs->pkiStatus])
194 )
195 }
196 break;
197 case OID_PKI_FAIL_INFO:
198 if (object.len == 1
199 && *object.ptr >= '0' && *object.ptr <= '4')
200 {
201 attrs->failInfo = (failInfo_t)(*object.ptr - '0');
202 }
203 if (attrs->failInfo != SCEP_unknown_REASON)
204 plog("failInfo: %s", failInfo_reasons[attrs->failInfo]);
205 break;
206 case OID_PKI_SENDER_NONCE:
207 attrs->senderNonce = object;
208 break;
209 case OID_PKI_RECIPIENT_NONCE:
210 attrs->recipientNonce = object;
211 break;
212 case OID_PKI_TRANS_ID:
213 attrs->transID = object;
214 }
215 return TRUE;
216 }
217
218 /**
219 * Parse X.501 attributes
220 */
221 bool parse_attributes(chunk_t blob, scep_attributes_t *attrs)
222 {
223 asn1_parser_t *parser;
224 chunk_t object;
225 int oid = OID_UNKNOWN;
226 int objectID;
227 bool success = FALSE;
228
229 parser = asn1_parser_create(attributesObjects, blob);
230 DBG(DBG_CONTROL | DBG_PARSING,
231 DBG_log("parsing attributes")
232 )
233
234 while (parser->iterate(parser, &objectID, &object))
235 {
236 switch (objectID)
237 {
238 case ATTRIBUTE_OBJ_TYPE:
239 oid = asn1_known_oid(object);
240 break;
241 case ATTRIBUTE_OBJ_VALUE:
242 if (!extract_attribute(oid, object, parser->get_level(parser), attrs))
243 {
244 goto end;
245 }
246 }
247 }
248 success = parser->success(parser);
249
250 end:
251 parser->destroy(parser);
252 return success;
253 }
254
255 /**
256 * Generates a unique fingerprint of the pkcs10 request
257 * by computing an MD5 hash over it
258 */
259 chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10)
260 {
261 chunk_t digest = chunk_alloca(HASH_SIZE_MD5);
262 hasher_t *hasher;
263
264 hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
265 hasher->get_hash(hasher, pkcs10, digest.ptr);
266 hasher->destroy(hasher);
267
268 return chunk_to_hex(digest, NULL, FALSE);
269 }
270
271 /**
272 * Generate a transaction id as the MD5 hash of an public key
273 * the transaction id is also used as a unique serial number
274 */
275 void scep_generate_transaction_id(public_key_t *key, chunk_t *transID,
276 chunk_t *serialNumber)
277 {
278 chunk_t digest = chunk_alloca(HASH_SIZE_MD5);
279 chunk_t keyEncoding = chunk_empty, keyInfo;
280 hasher_t *hasher;
281 bool msb_set;
282 u_char *pos;
283
284 key->get_encoding(key, PUBKEY_ASN1_DER, &keyEncoding);
285
286 keyInfo = asn1_wrap(ASN1_SEQUENCE, "mm",
287 asn1_algorithmIdentifier(OID_RSA_ENCRYPTION),
288 asn1_bitstring("m", keyEncoding));
289
290 hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
291 hasher->get_hash(hasher, keyInfo, digest.ptr);
292 hasher->destroy(hasher);
293 free(keyInfo.ptr);
294
295 /* is the most significant bit of the digest set? */
296 msb_set = (*digest.ptr & 0x80) == 0x80;
297
298 /* allocate space for the serialNumber */
299 serialNumber->len = msb_set + digest.len;
300 serialNumber->ptr = malloc(serialNumber->len);
301
302 /* the serial number as the two's complement of the digest */
303 pos = serialNumber->ptr;
304 if (msb_set)
305 {
306 *pos++ = 0x00;
307 }
308 memcpy(pos, digest.ptr, digest.len);
309
310 /* the transaction id is the serial number in hex format */
311 transID->len = 2*digest.len;
312 transID->ptr = malloc(transID->len + 1);
313 datatot(digest.ptr, digest.len, 16, transID->ptr, transID->len + 1);
314 }
315
316 /**
317 * Builds a transId attribute
318 */
319 chunk_t scep_transId_attribute(chunk_t transID)
320 {
321 return asn1_wrap(ASN1_SEQUENCE, "cm"
322 , ASN1_transId_oid
323 , asn1_wrap(ASN1_SET, "m"
324 , asn1_simple_object(ASN1_PRINTABLESTRING, transID)
325 )
326 );
327 }
328
329 /**
330 * Builds a messageType attribute
331 */
332 chunk_t scep_messageType_attribute(scep_msg_t m)
333 {
334 chunk_t msgType = {
335 (u_char*)msgType_values[m],
336 strlen(msgType_values[m])
337 };
338
339 return asn1_wrap(ASN1_SEQUENCE, "cm"
340 , ASN1_messageType_oid
341 , asn1_wrap(ASN1_SET, "m"
342 , asn1_simple_object(ASN1_PRINTABLESTRING, msgType)
343 )
344 );
345 }
346
347 /**
348 * Builds a senderNonce attribute
349 */
350 chunk_t scep_senderNonce_attribute(void)
351 {
352 const size_t nonce_len = 16;
353 u_char nonce_buf[nonce_len];
354 chunk_t senderNonce = { nonce_buf, nonce_len };
355 rng_t *rng;
356
357 rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
358 rng->get_bytes(rng, nonce_len, nonce_buf);
359 rng->destroy(rng);
360
361 return asn1_wrap(ASN1_SEQUENCE, "cm"
362 , ASN1_senderNonce_oid
363 , asn1_wrap(ASN1_SET, "m"
364 , asn1_simple_object(ASN1_OCTET_STRING, senderNonce)
365 )
366 );
367 }
368
369 /**
370 * Builds a pkcs7 enveloped and signed scep request
371 */
372 chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg,
373 certificate_t *enc_cert, int enc_alg,
374 certificate_t *signer_cert, int digest_alg,
375 private_key_t *private_key)
376 {
377 chunk_t envelopedData, attributes, request;
378
379 envelopedData = pkcs7_build_envelopedData(data, enc_cert, enc_alg);
380
381 attributes = asn1_wrap(ASN1_SET, "mmmmm"
382 , pkcs7_contentType_attribute()
383 , pkcs7_messageDigest_attribute(envelopedData
384 , digest_alg)
385 , scep_transId_attribute(transID)
386 , scep_messageType_attribute(msg)
387 , scep_senderNonce_attribute());
388
389 request = pkcs7_build_signedData(envelopedData, attributes
390 , signer_cert, digest_alg, private_key);
391 free(envelopedData.ptr);
392 free(attributes.ptr);
393 return request;
394 }
395
396 /**
397 * Converts a binary request to base64 with 64 characters per line
398 * newline and '+' characters are escaped by %0A and %2B, respectively
399 */
400 static char* escape_http_request(chunk_t req)
401 {
402 char *escaped_req = NULL;
403 char *p1, *p2;
404 int lines = 0;
405 int plus = 0;
406 int n = 0;
407
408 /* compute and allocate the size of the base64-encoded request */
409 int len = 1 + 4*((req.len + 2)/3);
410 char *encoded_req = malloc(len);
411
412 /* do the base64 conversion */
413 len = datatot(req.ptr, req.len, 64, encoded_req, len);
414
415 /* compute newline characters to be inserted every 64 characters */
416 lines = (len - 2) / 64;
417
418 /* count number of + characters to be escaped */
419 p1 = encoded_req;
420 while (*p1 != '\0')
421 {
422 if (*p1++ == '+')
423 plus++;
424 }
425
426 escaped_req = malloc(len + 3*(lines + plus));
427
428 /* escape special characters in the request */
429 p1 = encoded_req;
430 p2 = escaped_req;
431 while (*p1 != '\0')
432 {
433 if (n == 64)
434 {
435 memcpy(p2, "%0A", 3);
436 p2 += 3;
437 n = 0;
438 }
439 if (*p1 == '+')
440 {
441 memcpy(p2, "%2B", 3);
442 p2 += 3;
443 }
444 else
445 {
446 *p2++ = *p1;
447 }
448 p1++;
449 n++;
450 }
451 *p2 = '\0';
452 free(encoded_req);
453 return escaped_req;
454 }
455
456 /**
457 * Send a SCEP request via HTTP and wait for a response
458 */
459 bool scep_http_request(const char *url, chunk_t pkcs7, scep_op_t op,
460 bool http_get_request, chunk_t *response)
461 {
462 int len;
463 status_t status;
464 char *complete_url = NULL;
465
466 /* initialize response */
467 *response = chunk_empty;
468
469 DBG(DBG_CONTROL,
470 DBG_log("sending scep request to '%s'", url)
471 )
472
473 if (op == SCEP_PKI_OPERATION)
474 {
475 const char operation[] = "PKIOperation";
476
477 if (http_get_request)
478 {
479 char *escaped_req = escape_http_request(pkcs7);
480
481 /* form complete url */
482 len = strlen(url) + 20 + strlen(operation) + strlen(escaped_req) + 1;
483 complete_url = malloc(len);
484 snprintf(complete_url, len, "%s?operation=%s&message=%s"
485 , url, operation, escaped_req);
486 free(escaped_req);
487
488 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
489 FETCH_HTTP_VERSION_1_0,
490 FETCH_REQUEST_HEADER, "Pragma:",
491 FETCH_REQUEST_HEADER, "Host:",
492 FETCH_REQUEST_HEADER, "Accept:",
493 FETCH_END);
494 }
495 else /* HTTP_POST */
496 {
497 /* form complete url */
498 len = strlen(url) + 11 + strlen(operation) + 1;
499 complete_url = malloc(len);
500 snprintf(complete_url, len, "%s?operation=%s", url, operation);
501
502 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
503 FETCH_REQUEST_DATA, pkcs7,
504 FETCH_REQUEST_TYPE, "",
505 FETCH_REQUEST_HEADER, "Expect:",
506 FETCH_END);
507 }
508 }
509 else /* SCEP_GET_CA_CERT */
510 {
511 const char operation[] = "GetCACert";
512
513 /* form complete url */
514 len = strlen(url) + 32 + strlen(operation) + 1;
515 complete_url = malloc(len);
516 snprintf(complete_url, len, "%s?operation=%s&message=CAIdentifier"
517 , url, operation);
518
519 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
520 FETCH_END);
521 }
522
523 free(complete_url);
524 return (status == SUCCESS);
525 }
526
527 err_t scep_parse_response(chunk_t response, chunk_t transID, contentInfo_t *data,
528 scep_attributes_t *attrs, certificate_t *signer_cert)
529 {
530 chunk_t attributes;
531
532 if (!pkcs7_parse_signedData(response, data, NULL, &attributes, signer_cert))
533 {
534 return "error parsing the scep response";
535 }
536 if (!parse_attributes(attributes, attrs))
537 {
538 return "error parsing the scep response attributes";
539 }
540 if (!chunk_equals(transID, attrs->transID))
541 {
542 return "transaction ID of scep response does not match";
543 }
544 return NULL;
545 }