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