]> git.ipfire.org Git - thirdparty/strongswan.git/blob - src/scepclient/scep.c
Update copyright headers after acquisition by secunet
[thirdparty/strongswan.git] / src / scepclient / scep.c
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2005 Jan Hutter, Martin Willi
4 *
5 * Copyright (C) secunet Security Networks AG
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 #include <string.h>
19 #include <stdlib.h>
20
21 #include <library.h>
22 #include <utils/debug.h>
23 #include <asn1/asn1.h>
24 #include <asn1/asn1_parser.h>
25 #include <asn1/oid.h>
26 #include <crypto/rngs/rng.h>
27 #include <crypto/hashers/hasher.h>
28
29 #include "scep.h"
30
31 static const char *pkiStatus_values[] = { "0", "2", "3" };
32
33 static const char *pkiStatus_names[] = {
34 "SUCCESS",
35 "FAILURE",
36 "PENDING",
37 "UNKNOWN"
38 };
39
40 static const char *msgType_values[] = { "3", "19", "20", "21", "22" };
41
42 static const char *msgType_names[] = {
43 "CertRep",
44 "PKCSReq",
45 "GetCertInitial",
46 "GetCert",
47 "GetCRL",
48 "Unknown"
49 };
50
51 static const char *failInfo_reasons[] = {
52 "badAlg - unrecognized or unsupported algorithm identifier",
53 "badMessageCheck - integrity check failed",
54 "badRequest - transaction not permitted or supported",
55 "badTime - Message time field was not sufficiently close to the system time",
56 "badCertId - No certificate could be identified matching the provided criteria"
57 };
58
59 const scep_attributes_t empty_scep_attributes = {
60 SCEP_Unknown_MSG , /* msgType */
61 SCEP_UNKNOWN , /* pkiStatus */
62 SCEP_unknown_REASON, /* failInfo */
63 { NULL, 0 } , /* transID */
64 { NULL, 0 } , /* senderNonce */
65 { NULL, 0 } , /* recipientNonce */
66 };
67
68 /**
69 * Extract X.501 attributes
70 */
71 void extract_attributes(pkcs7_t *pkcs7, enumerator_t *enumerator,
72 scep_attributes_t *attrs)
73 {
74 chunk_t attr;
75
76 if (pkcs7->get_attribute(pkcs7, OID_PKI_MESSAGE_TYPE, enumerator, &attr))
77 {
78 scep_msg_t m;
79
80 for (m = SCEP_CertRep_MSG; m < SCEP_Unknown_MSG; m++)
81 {
82 if (strncmp(msgType_values[m], attr.ptr, attr.len) == 0)
83 {
84 attrs->msgType = m;
85 }
86 }
87 DBG2(DBG_APP, "messageType: %s", msgType_names[attrs->msgType]);
88 free(attr.ptr);
89 }
90 if (pkcs7->get_attribute(pkcs7, OID_PKI_STATUS, enumerator, &attr))
91 {
92 pkiStatus_t s;
93
94 for (s = SCEP_SUCCESS; s < SCEP_UNKNOWN; s++)
95 {
96 if (strncmp(pkiStatus_values[s], attr.ptr, attr.len) == 0)
97 {
98 attrs->pkiStatus = s;
99 }
100 }
101 DBG2(DBG_APP, "pkiStatus: %s", pkiStatus_names[attrs->pkiStatus]);
102 free(attr.ptr);
103 }
104 if (pkcs7->get_attribute(pkcs7, OID_PKI_FAIL_INFO, enumerator, &attr))
105 {
106 if (attr.len == 1 && *attr.ptr >= '0' && *attr.ptr <= '4')
107 {
108 attrs->failInfo = (failInfo_t)(*attr.ptr - '0');
109 }
110 if (attrs->failInfo != SCEP_unknown_REASON)
111 {
112 DBG1(DBG_APP, "failInfo: %s", failInfo_reasons[attrs->failInfo]);
113 }
114 free(attr.ptr);
115 }
116
117 pkcs7->get_attribute(pkcs7, OID_PKI_SENDER_NONCE, enumerator,
118 &attrs->senderNonce);
119 pkcs7->get_attribute(pkcs7, OID_PKI_RECIPIENT_NONCE, enumerator,
120 &attrs->recipientNonce);
121 pkcs7->get_attribute(pkcs7, OID_PKI_TRANS_ID, enumerator,
122 &attrs->transID);
123 }
124
125 /**
126 * Generates a unique fingerprint of the pkcs10 request
127 * by computing an MD5 hash over it
128 */
129 chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10)
130 {
131 chunk_t digest = chunk_alloca(HASH_SIZE_MD5);
132 hasher_t *hasher;
133
134 hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
135 if (!hasher || !hasher->get_hash(hasher, pkcs10, digest.ptr))
136 {
137 DESTROY_IF(hasher);
138 return chunk_empty;
139 }
140 hasher->destroy(hasher);
141
142 return chunk_to_hex(digest, NULL, FALSE);
143 }
144
145 /**
146 * Generate a transaction id as the MD5 hash of an public key
147 * the transaction id is also used as a unique serial number
148 */
149 void scep_generate_transaction_id(public_key_t *key, chunk_t *transID,
150 chunk_t *serialNumber)
151 {
152 chunk_t digest = chunk_alloca(HASH_SIZE_MD5);
153 chunk_t keyEncoding = chunk_empty, keyInfo;
154 hasher_t *hasher;
155 int zeros = 0, msb_set = 0;
156
157 key->get_encoding(key, PUBKEY_ASN1_DER, &keyEncoding);
158
159 keyInfo = asn1_wrap(ASN1_SEQUENCE, "mm",
160 asn1_algorithmIdentifier(OID_RSA_ENCRYPTION),
161 asn1_bitstring("m", keyEncoding));
162
163 hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
164 if (!hasher || !hasher->get_hash(hasher, keyInfo, digest.ptr))
165 {
166 memset(digest.ptr, 0, digest.len);
167 }
168 DESTROY_IF(hasher);
169 free(keyInfo.ptr);
170
171 /* the serialNumber should be valid ASN1 integer content:
172 * remove leading zeros, add one if MSB is set (two's complement) */
173 while (zeros < digest.len)
174 {
175 if (digest.ptr[zeros])
176 {
177 if (digest.ptr[zeros] & 0x80)
178 {
179 msb_set = 1;
180 }
181 break;
182 }
183 zeros++;
184 }
185 *serialNumber = chunk_alloc(digest.len - zeros + msb_set);
186 if (msb_set)
187 {
188 serialNumber->ptr[0] = 0x00;
189 }
190 memcpy(serialNumber->ptr + msb_set, digest.ptr + zeros,
191 digest.len - zeros);
192
193 /* the transaction id is the serial number in hex format */
194 *transID = chunk_to_hex(digest, NULL, TRUE);
195 }
196
197 /**
198 * Builds a pkcs7 enveloped and signed scep request
199 */
200 chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg,
201 certificate_t *enc_cert, encryption_algorithm_t enc_alg,
202 size_t key_size, certificate_t *signer_cert,
203 hash_algorithm_t digest_alg, private_key_t *private_key)
204 {
205 chunk_t request;
206 container_t *container;
207 char nonce[16];
208 rng_t *rng;
209 chunk_t senderNonce, msgType;
210
211 /* generate senderNonce */
212 rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
213 if (!rng || !rng->get_bytes(rng, sizeof(nonce), nonce))
214 {
215 DESTROY_IF(rng);
216 return chunk_empty;
217 }
218 rng->destroy(rng);
219
220 /* encrypt data in enveloped-data PKCS#7 */
221 container = lib->creds->create(lib->creds,
222 CRED_CONTAINER, CONTAINER_PKCS7_ENVELOPED_DATA,
223 BUILD_BLOB, data,
224 BUILD_CERT, enc_cert,
225 BUILD_ENCRYPTION_ALG, enc_alg,
226 BUILD_KEY_SIZE, (int)key_size,
227 BUILD_END);
228 if (!container)
229 {
230 return chunk_empty;
231 }
232 if (!container->get_encoding(container, &request))
233 {
234 container->destroy(container);
235 return chunk_empty;
236 }
237 container->destroy(container);
238
239 /* sign enveloped-data in a signed-data PKCS#7 */
240 senderNonce = asn1_wrap(ASN1_OCTET_STRING, "c", chunk_from_thing(nonce));
241 transID = asn1_wrap(ASN1_PRINTABLESTRING, "c", transID);
242 msgType = asn1_wrap(ASN1_PRINTABLESTRING, "c",
243 chunk_create((char*)msgType_values[msg],
244 strlen(msgType_values[msg])));
245
246 container = lib->creds->create(lib->creds,
247 CRED_CONTAINER, CONTAINER_PKCS7_SIGNED_DATA,
248 BUILD_BLOB, request,
249 BUILD_SIGNING_CERT, signer_cert,
250 BUILD_SIGNING_KEY, private_key,
251 BUILD_DIGEST_ALG, digest_alg,
252 BUILD_PKCS7_ATTRIBUTE, OID_PKI_SENDER_NONCE, senderNonce,
253 BUILD_PKCS7_ATTRIBUTE, OID_PKI_TRANS_ID, transID,
254 BUILD_PKCS7_ATTRIBUTE, OID_PKI_MESSAGE_TYPE, msgType,
255 BUILD_END);
256
257 free(request.ptr);
258 free(senderNonce.ptr);
259 free(transID.ptr);
260 free(msgType.ptr);
261
262 if (!container)
263 {
264 return chunk_empty;
265 }
266 if (!container->get_encoding(container, &request))
267 {
268 container->destroy(container);
269 return chunk_empty;
270 }
271 container->destroy(container);
272
273 return request;
274 }
275
276 /**
277 * Converts a binary request to base64 with 64 characters per line
278 * newline and '+' characters are escaped by %0A and %2B, respectively
279 */
280 static char* escape_http_request(chunk_t req)
281 {
282 char *escaped_req = NULL;
283 char *p1, *p2;
284 int lines = 0;
285 int plus = 0;
286 int n = 0;
287
288 /* compute and allocate the size of the base64-encoded request */
289 int len = 1 + 4 * ((req.len + 2) / 3);
290 char *encoded_req = malloc(len);
291
292 /* do the base64 conversion */
293 chunk_t base64 = chunk_to_base64(req, encoded_req);
294 len = base64.len + 1;
295
296 /* compute newline characters to be inserted every 64 characters */
297 lines = (len - 2) / 64;
298
299 /* count number of + characters to be escaped */
300 p1 = encoded_req;
301 while (*p1 != '\0')
302 {
303 if (*p1++ == '+')
304 {
305 plus++;
306 }
307 }
308
309 escaped_req = malloc(len + 3 * (lines + plus));
310
311 /* escape special characters in the request */
312 p1 = encoded_req;
313 p2 = escaped_req;
314 while (*p1 != '\0')
315 {
316 if (n == 64)
317 {
318 memcpy(p2, "%0A", 3);
319 p2 += 3;
320 n = 0;
321 }
322 if (*p1 == '+')
323 {
324 memcpy(p2, "%2B", 3);
325 p2 += 3;
326 }
327 else
328 {
329 *p2++ = *p1;
330 }
331 p1++;
332 n++;
333 }
334 *p2 = '\0';
335 free(encoded_req);
336 return escaped_req;
337 }
338
339 /**
340 * Send a SCEP request via HTTP and wait for a response
341 */
342 bool scep_http_request(const char *url, chunk_t msg, scep_op_t op,
343 bool http_get_request, u_int timeout, char *src,
344 chunk_t *response)
345 {
346 int len;
347 status_t status;
348 char *complete_url = NULL;
349 host_t *srcip = NULL;
350
351 /* initialize response */
352 *response = chunk_empty;
353
354 if (src)
355 {
356 srcip = host_create_from_string(src, 0);
357 }
358
359 DBG2(DBG_APP, "sending scep request to '%s'", url);
360
361 if (op == SCEP_PKI_OPERATION)
362 {
363 const char operation[] = "PKIOperation";
364
365 if (http_get_request)
366 {
367 char *escaped_req = escape_http_request(msg);
368
369 /* form complete url */
370 len = strlen(url) + 20 + strlen(operation) + strlen(escaped_req) + 1;
371 complete_url = malloc(len);
372 snprintf(complete_url, len, "%s?operation=%s&message=%s"
373 , url, operation, escaped_req);
374 free(escaped_req);
375
376 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
377 FETCH_HTTP_VERSION_1_0,
378 FETCH_TIMEOUT, timeout,
379 FETCH_REQUEST_HEADER, "Pragma:",
380 FETCH_REQUEST_HEADER, "Host:",
381 FETCH_REQUEST_HEADER, "Accept:",
382 FETCH_SOURCEIP, srcip,
383 FETCH_END);
384 }
385 else /* HTTP_POST */
386 {
387 /* form complete url */
388 len = strlen(url) + 11 + strlen(operation) + 1;
389 complete_url = malloc(len);
390 snprintf(complete_url, len, "%s?operation=%s", url, operation);
391
392 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
393 FETCH_HTTP_VERSION_1_0,
394 FETCH_TIMEOUT, timeout,
395 FETCH_REQUEST_DATA, msg,
396 FETCH_REQUEST_TYPE, "",
397 FETCH_REQUEST_HEADER, "Expect:",
398 FETCH_SOURCEIP, srcip,
399 FETCH_END);
400 }
401 }
402 else /* SCEP_GET_CA_CERT */
403 {
404 const char operation[] = "GetCACert";
405 int i;
406
407 /* escape spaces, TODO: complete URL escape */
408 for (i = 0; i < msg.len; i++)
409 {
410 if (msg.ptr[i] == ' ')
411 {
412 msg.ptr[i] = '+';
413 }
414 }
415
416 /* form complete url */
417 len = strlen(url) + 32 + strlen(operation) + msg.len + 1;
418 complete_url = malloc(len);
419 snprintf(complete_url, len, "%s?operation=%s&message=%.*s",
420 url, operation, (int)msg.len, msg.ptr);
421
422 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
423 FETCH_HTTP_VERSION_1_0,
424 FETCH_TIMEOUT, timeout,
425 FETCH_SOURCEIP, srcip,
426 FETCH_END);
427 }
428
429 DESTROY_IF(srcip);
430 free(complete_url);
431 return (status == SUCCESS);
432 }
433
434 err_t scep_parse_response(chunk_t response, chunk_t transID,
435 container_t **out, scep_attributes_t *attrs)
436 {
437 enumerator_t *enumerator;
438 bool verified = FALSE;
439 container_t *container;
440 auth_cfg_t *auth;
441
442 container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7,
443 BUILD_BLOB_ASN1_DER, response, BUILD_END);
444 if (!container)
445 {
446 return "error parsing the scep response";
447 }
448 if (container->get_type(container) != CONTAINER_PKCS7_SIGNED_DATA)
449 {
450 container->destroy(container);
451 return "scep response is not PKCS#7 signed-data";
452 }
453
454 enumerator = container->create_signature_enumerator(container);
455 while (enumerator->enumerate(enumerator, &auth))
456 {
457 verified = TRUE;
458 extract_attributes((pkcs7_t*)container, enumerator, attrs);
459 if (!chunk_equals(transID, attrs->transID))
460 {
461 enumerator->destroy(enumerator);
462 container->destroy(container);
463 return "transaction ID of scep response does not match";
464 }
465 }
466 enumerator->destroy(enumerator);
467 if (!verified)
468 {
469 container->destroy(container);
470 return "unable to verify PKCS#7 container";
471 }
472 *out = container;
473 return NULL;
474 }