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