Migrated scepclient to new modular PKCS# API
[strongswan.git] / src / scepclient / scep.c
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2005 Jan Hutter, Martin Willi
4 * 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/pkcs9.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 }
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 }
102 if (pkcs7->get_attribute(pkcs7, OID_PKI_FAIL_INFO, enumerator, &attr))
103 {
104 if (attr.len == 1 && *attr.ptr >= '0' && *attr.ptr <= '4')
105 {
106 attrs->failInfo = (failInfo_t)(*attr.ptr - '0');
107 }
108 if (attrs->failInfo != SCEP_unknown_REASON)
109 {
110 DBG1(DBG_APP, "failInfo: %s", failInfo_reasons[attrs->failInfo]);
111 }
112 }
113
114 pkcs7->get_attribute(pkcs7, OID_PKI_SENDER_NONCE, enumerator,
115 &attrs->senderNonce);
116 pkcs7->get_attribute(pkcs7, OID_PKI_RECIPIENT_NONCE, enumerator,
117 &attrs->recipientNonce);
118 pkcs7->get_attribute(pkcs7, OID_PKI_TRANS_ID, enumerator,
119 &attrs->transID);
120 }
121
122 /**
123 * Generates a unique fingerprint of the pkcs10 request
124 * by computing an MD5 hash over it
125 */
126 chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10)
127 {
128 chunk_t digest = chunk_alloca(HASH_SIZE_MD5);
129 hasher_t *hasher;
130
131 hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
132 if (!hasher || !hasher->get_hash(hasher, pkcs10, digest.ptr))
133 {
134 DESTROY_IF(hasher);
135 return chunk_empty;
136 }
137 hasher->destroy(hasher);
138
139 return chunk_to_hex(digest, NULL, FALSE);
140 }
141
142 /**
143 * Generate a transaction id as the MD5 hash of an public key
144 * the transaction id is also used as a unique serial number
145 */
146 void scep_generate_transaction_id(public_key_t *key, chunk_t *transID,
147 chunk_t *serialNumber)
148 {
149 chunk_t digest = chunk_alloca(HASH_SIZE_MD5);
150 chunk_t keyEncoding = chunk_empty, keyInfo;
151 hasher_t *hasher;
152 bool msb_set;
153 u_char *pos;
154
155 key->get_encoding(key, PUBKEY_ASN1_DER, &keyEncoding);
156
157 keyInfo = asn1_wrap(ASN1_SEQUENCE, "mm",
158 asn1_algorithmIdentifier(OID_RSA_ENCRYPTION),
159 asn1_bitstring("m", keyEncoding));
160
161 hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
162 if (!hasher || !hasher->get_hash(hasher, keyInfo, digest.ptr))
163 {
164 memset(digest.ptr, 0, digest.len);
165 }
166 DESTROY_IF(hasher);
167 free(keyInfo.ptr);
168
169 /* is the most significant bit of the digest set? */
170 msb_set = (*digest.ptr & 0x80) == 0x80;
171
172 /* allocate space for the serialNumber */
173 serialNumber->len = msb_set + digest.len;
174 serialNumber->ptr = malloc(serialNumber->len);
175
176 /* the serial number as the two's complement of the digest */
177 pos = serialNumber->ptr;
178 if (msb_set)
179 {
180 *pos++ = 0x00;
181 }
182 memcpy(pos, digest.ptr, digest.len);
183
184 /* the transaction id is the serial number in hex format */
185 *transID = chunk_to_hex(digest, NULL, TRUE);
186 }
187
188 /**
189 * Builds a pkcs7 enveloped and signed scep request
190 */
191 chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg,
192 certificate_t *enc_cert, encryption_algorithm_t enc_alg,
193 size_t key_size, certificate_t *signer_cert,
194 hash_algorithm_t digest_alg, private_key_t *private_key)
195 {
196 chunk_t request;
197 container_t *container;
198 char nonce[16];
199 rng_t *rng;
200 chunk_t senderNonce, msgType;
201
202 /* generate senderNonce */
203 rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
204 if (!rng || !rng->get_bytes(rng, sizeof(nonce), nonce))
205 {
206 DESTROY_IF(rng);
207 return chunk_empty;
208 }
209 rng->destroy(rng);
210
211 /* encrypt data in enveloped-data PKCS#7 */
212 container = lib->creds->create(lib->creds,
213 CRED_CONTAINER, CONTAINER_PKCS7_ENVELOPED_DATA,
214 BUILD_BLOB, data,
215 BUILD_CERT, enc_cert,
216 BUILD_ENCRYPTION_ALG, enc_alg,
217 BUILD_KEY_SIZE, (int)key_size,
218 BUILD_END);
219 if (!container)
220 {
221 return chunk_empty;
222 }
223 if (!container->get_encoding(container, &request))
224 {
225 container->destroy(container);
226 return chunk_empty;
227 }
228 container->destroy(container);
229
230 /* sign enveloped-data in a signed-data PKCS#7 */
231 senderNonce = asn1_wrap(ASN1_OCTET_STRING, "c", chunk_from_thing(nonce));
232 transID = asn1_wrap(ASN1_PRINTABLESTRING, "c", transID);
233 msgType = asn1_wrap(ASN1_PRINTABLESTRING, "c",
234 chunk_create((char*)msgType_values[msg],
235 strlen(msgType_values[msg])));
236
237 container = lib->creds->create(lib->creds,
238 CRED_CONTAINER, CONTAINER_PKCS7_SIGNED_DATA,
239 BUILD_BLOB, request,
240 BUILD_SIGNING_CERT, signer_cert,
241 BUILD_SIGNING_KEY, private_key,
242 BUILD_DIGEST_ALG, digest_alg,
243 BUILD_PKCS7_ATTRIBUTE, OID_PKI_SENDER_NONCE, senderNonce,
244 BUILD_PKCS7_ATTRIBUTE, OID_PKI_TRANS_ID, transID,
245 BUILD_PKCS7_ATTRIBUTE, OID_PKI_MESSAGE_TYPE, msgType,
246 BUILD_END);
247
248 free(request.ptr);
249 free(senderNonce.ptr);
250 free(transID.ptr);
251 free(msgType.ptr);
252
253 if (!container)
254 {
255 return chunk_empty;
256 }
257 if (!container->get_encoding(container, &request))
258 {
259 container->destroy(container);
260 return chunk_empty;
261 }
262 container->destroy(container);
263
264 return request;
265 }
266
267 /**
268 * Converts a binary request to base64 with 64 characters per line
269 * newline and '+' characters are escaped by %0A and %2B, respectively
270 */
271 static char* escape_http_request(chunk_t req)
272 {
273 char *escaped_req = NULL;
274 char *p1, *p2;
275 int lines = 0;
276 int plus = 0;
277 int n = 0;
278
279 /* compute and allocate the size of the base64-encoded request */
280 int len = 1 + 4 * ((req.len + 2) / 3);
281 char *encoded_req = malloc(len);
282
283 /* do the base64 conversion */
284 chunk_t base64 = chunk_to_base64(req, encoded_req);
285 len = base64.len + 1;
286
287 /* compute newline characters to be inserted every 64 characters */
288 lines = (len - 2) / 64;
289
290 /* count number of + characters to be escaped */
291 p1 = encoded_req;
292 while (*p1 != '\0')
293 {
294 if (*p1++ == '+')
295 {
296 plus++;
297 }
298 }
299
300 escaped_req = malloc(len + 3 * (lines + plus));
301
302 /* escape special characters in the request */
303 p1 = encoded_req;
304 p2 = escaped_req;
305 while (*p1 != '\0')
306 {
307 if (n == 64)
308 {
309 memcpy(p2, "%0A", 3);
310 p2 += 3;
311 n = 0;
312 }
313 if (*p1 == '+')
314 {
315 memcpy(p2, "%2B", 3);
316 p2 += 3;
317 }
318 else
319 {
320 *p2++ = *p1;
321 }
322 p1++;
323 n++;
324 }
325 *p2 = '\0';
326 free(encoded_req);
327 return escaped_req;
328 }
329
330 /**
331 * Send a SCEP request via HTTP and wait for a response
332 */
333 bool scep_http_request(const char *url, chunk_t msg, scep_op_t op,
334 bool http_get_request, chunk_t *response)
335 {
336 int len;
337 status_t status;
338 char *complete_url = NULL;
339
340 /* initialize response */
341 *response = chunk_empty;
342
343 DBG2(DBG_APP, "sending scep request to '%s'", url);
344
345 if (op == SCEP_PKI_OPERATION)
346 {
347 const char operation[] = "PKIOperation";
348
349 if (http_get_request)
350 {
351 char *escaped_req = escape_http_request(msg);
352
353 /* form complete url */
354 len = strlen(url) + 20 + strlen(operation) + strlen(escaped_req) + 1;
355 complete_url = malloc(len);
356 snprintf(complete_url, len, "%s?operation=%s&message=%s"
357 , url, operation, escaped_req);
358 free(escaped_req);
359
360 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
361 FETCH_HTTP_VERSION_1_0,
362 FETCH_REQUEST_HEADER, "Pragma:",
363 FETCH_REQUEST_HEADER, "Host:",
364 FETCH_REQUEST_HEADER, "Accept:",
365 FETCH_END);
366 }
367 else /* HTTP_POST */
368 {
369 /* form complete url */
370 len = strlen(url) + 11 + strlen(operation) + 1;
371 complete_url = malloc(len);
372 snprintf(complete_url, len, "%s?operation=%s", url, operation);
373
374 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
375 FETCH_HTTP_VERSION_1_0,
376 FETCH_REQUEST_DATA, msg,
377 FETCH_REQUEST_TYPE, "",
378 FETCH_REQUEST_HEADER, "Expect:",
379 FETCH_END);
380 }
381 }
382 else /* SCEP_GET_CA_CERT */
383 {
384 const char operation[] = "GetCACert";
385 int i;
386
387 /* escape spaces, TODO: complete URL escape */
388 for (i = 0; i < msg.len; i++)
389 {
390 if (msg.ptr[i] == ' ')
391 {
392 msg.ptr[i] = '+';
393 }
394 }
395
396 /* form complete url */
397 len = strlen(url) + 32 + strlen(operation) + msg.len + 1;
398 complete_url = malloc(len);
399 snprintf(complete_url, len, "%s?operation=%s&message=%.*s",
400 url, operation, (int)msg.len, msg.ptr);
401
402 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
403 FETCH_HTTP_VERSION_1_0,
404 FETCH_END);
405 }
406
407 free(complete_url);
408 return (status == SUCCESS);
409 }
410
411 err_t scep_parse_response(chunk_t response, chunk_t transID,
412 container_t **out, scep_attributes_t *attrs)
413 {
414 enumerator_t *enumerator;
415 bool verified = FALSE;
416 container_t *container;
417 auth_cfg_t *auth;
418
419 container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7,
420 BUILD_BLOB_ASN1_DER, response, BUILD_END);
421 if (!container)
422 {
423 return "error parsing the scep response";
424 }
425 if (container->get_type(container) != CONTAINER_PKCS7_SIGNED_DATA)
426 {
427 container->destroy(container);
428 return "scep response is not PKCS#7 signed-data";
429 }
430
431 enumerator = container->create_signature_enumerator(container);
432 while (enumerator->enumerate(enumerator, &auth))
433 {
434 verified = TRUE;
435 extract_attributes((pkcs7_t*)container, enumerator, attrs);
436 if (!chunk_equals(transID, attrs->transID))
437 {
438 enumerator->destroy(enumerator);
439 container->destroy(container);
440 return "transaction ID of scep response does not match";
441 }
442 }
443 enumerator->destroy(enumerator);
444 if (!verified)
445 {
446 container->destroy(container);
447 return "unable to verify PKCS#7 container";
448 }
449 *out = container;
450 return NULL;
451 }