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