f7a1f0b367825181a630d6a13de9f0f722076868
[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, scep_attributes_t *attrs)
72 {
73 pkcs9_t *attributes = pkcs7->get_attributes(pkcs7);
74 chunk_t attr;
75
76 attr = attributes->get_attribute(attributes, OID_PKI_MESSAGE_TYPE);
77 if (attr.ptr)
78 {
79 scep_msg_t m;
80
81 for (m = SCEP_CertRep_MSG; m < SCEP_Unknown_MSG; m++)
82 {
83 if (strncmp(msgType_values[m], attr.ptr, attr.len) == 0)
84 {
85 attrs->msgType = m;
86 }
87 }
88 DBG2(DBG_APP, "messageType: %s", msgType_names[attrs->msgType]);
89 }
90 attr = attributes->get_attribute(attributes, OID_PKI_STATUS);
91 if (attr.ptr)
92 {
93 pkiStatus_t s;
94
95 for (s = SCEP_SUCCESS; s < SCEP_UNKNOWN; s++)
96 {
97 if (strncmp(pkiStatus_values[s], attr.ptr, attr.len) == 0)
98 {
99 attrs->pkiStatus = s;
100 }
101 }
102 DBG2(DBG_APP, "pkiStatus: %s", pkiStatus_names[attrs->pkiStatus]);
103 }
104 attr = attributes->get_attribute(attributes, OID_PKI_FAIL_INFO);
105 if (attr.ptr)
106 {
107 if (attr.len == 1 && *attr.ptr >= '0' && *attr.ptr <= '4')
108 {
109 attrs->failInfo = (failInfo_t)(*attr.ptr - '0');
110 }
111 if (attrs->failInfo != SCEP_unknown_REASON)
112 {
113 DBG1(DBG_APP, "failInfo: %s", failInfo_reasons[attrs->failInfo]);
114 }
115 }
116 attrs->senderNonce = attributes->get_attribute(attributes,
117 OID_PKI_SENDER_NONCE);
118 attrs->recipientNonce = attributes->get_attribute(attributes,
119 OID_PKI_RECIPIENT_NONCE);
120 attrs->transID = attributes->get_attribute(attributes,
121 OID_PKI_TRANS_ID);
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 bool msb_set;
155 u_char *pos;
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 /* is the most significant bit of the digest set? */
172 msb_set = (*digest.ptr & 0x80) == 0x80;
173
174 /* allocate space for the serialNumber */
175 serialNumber->len = msb_set + digest.len;
176 serialNumber->ptr = malloc(serialNumber->len);
177
178 /* the serial number as the two's complement of the digest */
179 pos = serialNumber->ptr;
180 if (msb_set)
181 {
182 *pos++ = 0x00;
183 }
184 memcpy(pos, digest.ptr, digest.len);
185
186 /* the transaction id is the serial number in hex format */
187 *transID = chunk_to_hex(digest, NULL, TRUE);
188 }
189
190 /**
191 * Adds a senderNonce attribute to the given pkcs9 attribute list
192 */
193 static bool add_senderNonce_attribute(pkcs9_t *pkcs9)
194 {
195 const size_t nonce_len = 16;
196 u_char nonce_buf[nonce_len];
197 chunk_t senderNonce = { nonce_buf, nonce_len };
198 rng_t *rng;
199
200 rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
201 if (!rng || !rng->get_bytes(rng, nonce_len, nonce_buf))
202 {
203 DESTROY_IF(rng);
204 return FALSE;
205 }
206 rng->destroy(rng);
207
208 pkcs9->add_attribute(pkcs9, OID_PKI_SENDER_NONCE,
209 asn1_wrap(ASN1_OCTET_STRING, "c", senderNonce));
210 return TRUE;
211 }
212
213 /**
214 * Builds a pkcs7 enveloped and signed scep request
215 */
216 chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg,
217 certificate_t *enc_cert, encryption_algorithm_t enc_alg,
218 size_t key_size, certificate_t *signer_cert,
219 hash_algorithm_t digest_alg, private_key_t *private_key)
220 {
221 chunk_t request, msgType = {
222 (u_char*)msgType_values[msg],
223 strlen(msgType_values[msg]),
224 };
225 pkcs7_t *pkcs7;
226 pkcs9_t *pkcs9;
227
228 pkcs7 = pkcs7_create_from_data(data);
229 if (!pkcs7->build_envelopedData(pkcs7, enc_cert, enc_alg, key_size))
230 {
231 pkcs7->destroy(pkcs7);
232 return chunk_empty;
233 }
234
235 pkcs9 = pkcs9_create();
236 pkcs9->add_attribute(pkcs9, OID_PKI_TRANS_ID,
237 asn1_wrap(ASN1_PRINTABLESTRING, "c", transID));
238 pkcs9->add_attribute(pkcs9, OID_PKI_MESSAGE_TYPE,
239 asn1_wrap(ASN1_PRINTABLESTRING, "c", msgType));
240 if (!add_senderNonce_attribute(pkcs9))
241 {
242 pkcs9->destroy(pkcs9);
243 pkcs7->destroy(pkcs7);
244 return chunk_empty;
245 }
246
247 pkcs7->set_attributes(pkcs7, pkcs9);
248 pkcs7->set_certificate(pkcs7, signer_cert->get_ref(signer_cert));
249 if (!pkcs7->build_signedData(pkcs7, private_key, digest_alg))
250 {
251 pkcs7->destroy(pkcs7);
252 return chunk_empty;
253 }
254 request = pkcs7->get_contentInfo(pkcs7);
255 pkcs7->destroy(pkcs7);
256 return request;
257 }
258
259 /**
260 * Converts a binary request to base64 with 64 characters per line
261 * newline and '+' characters are escaped by %0A and %2B, respectively
262 */
263 static char* escape_http_request(chunk_t req)
264 {
265 char *escaped_req = NULL;
266 char *p1, *p2;
267 int lines = 0;
268 int plus = 0;
269 int n = 0;
270
271 /* compute and allocate the size of the base64-encoded request */
272 int len = 1 + 4 * ((req.len + 2) / 3);
273 char *encoded_req = malloc(len);
274
275 /* do the base64 conversion */
276 chunk_t base64 = chunk_to_base64(req, encoded_req);
277 len = base64.len + 1;
278
279 /* compute newline characters to be inserted every 64 characters */
280 lines = (len - 2) / 64;
281
282 /* count number of + characters to be escaped */
283 p1 = encoded_req;
284 while (*p1 != '\0')
285 {
286 if (*p1++ == '+')
287 {
288 plus++;
289 }
290 }
291
292 escaped_req = malloc(len + 3 * (lines + plus));
293
294 /* escape special characters in the request */
295 p1 = encoded_req;
296 p2 = escaped_req;
297 while (*p1 != '\0')
298 {
299 if (n == 64)
300 {
301 memcpy(p2, "%0A", 3);
302 p2 += 3;
303 n = 0;
304 }
305 if (*p1 == '+')
306 {
307 memcpy(p2, "%2B", 3);
308 p2 += 3;
309 }
310 else
311 {
312 *p2++ = *p1;
313 }
314 p1++;
315 n++;
316 }
317 *p2 = '\0';
318 free(encoded_req);
319 return escaped_req;
320 }
321
322 /**
323 * Send a SCEP request via HTTP and wait for a response
324 */
325 bool scep_http_request(const char *url, chunk_t msg, scep_op_t op,
326 bool http_get_request, chunk_t *response)
327 {
328 int len;
329 status_t status;
330 char *complete_url = NULL;
331
332 /* initialize response */
333 *response = chunk_empty;
334
335 DBG2(DBG_APP, "sending scep request to '%s'", url);
336
337 if (op == SCEP_PKI_OPERATION)
338 {
339 const char operation[] = "PKIOperation";
340
341 if (http_get_request)
342 {
343 char *escaped_req = escape_http_request(msg);
344
345 /* form complete url */
346 len = strlen(url) + 20 + strlen(operation) + strlen(escaped_req) + 1;
347 complete_url = malloc(len);
348 snprintf(complete_url, len, "%s?operation=%s&message=%s"
349 , url, operation, escaped_req);
350 free(escaped_req);
351
352 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
353 FETCH_HTTP_VERSION_1_0,
354 FETCH_REQUEST_HEADER, "Pragma:",
355 FETCH_REQUEST_HEADER, "Host:",
356 FETCH_REQUEST_HEADER, "Accept:",
357 FETCH_END);
358 }
359 else /* HTTP_POST */
360 {
361 /* form complete url */
362 len = strlen(url) + 11 + strlen(operation) + 1;
363 complete_url = malloc(len);
364 snprintf(complete_url, len, "%s?operation=%s", url, operation);
365
366 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
367 FETCH_HTTP_VERSION_1_0,
368 FETCH_REQUEST_DATA, msg,
369 FETCH_REQUEST_TYPE, "",
370 FETCH_REQUEST_HEADER, "Expect:",
371 FETCH_END);
372 }
373 }
374 else /* SCEP_GET_CA_CERT */
375 {
376 const char operation[] = "GetCACert";
377 int i;
378
379 /* escape spaces, TODO: complete URL escape */
380 for (i = 0; i < msg.len; i++)
381 {
382 if (msg.ptr[i] == ' ')
383 {
384 msg.ptr[i] = '+';
385 }
386 }
387
388 /* form complete url */
389 len = strlen(url) + 32 + strlen(operation) + msg.len + 1;
390 complete_url = malloc(len);
391 snprintf(complete_url, len, "%s?operation=%s&message=%.*s",
392 url, operation, (int)msg.len, msg.ptr);
393
394 status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
395 FETCH_HTTP_VERSION_1_0,
396 FETCH_END);
397 }
398
399 free(complete_url);
400 return (status == SUCCESS);
401 }
402
403 err_t scep_parse_response(chunk_t response, chunk_t transID, pkcs7_t **data,
404 scep_attributes_t *attrs, certificate_t *signer_cert)
405 {
406 pkcs7_t *pkcs7;
407
408 pkcs7 = pkcs7_create_from_chunk(response, 0);
409 if (!pkcs7 || !pkcs7->parse_signedData(pkcs7, signer_cert))
410 {
411 DESTROY_IF(pkcs7);
412 return "error parsing the scep response";
413 }
414 extract_attributes(pkcs7, attrs);
415 if (!chunk_equals(transID, attrs->transID))
416 {
417 pkcs7->destroy(pkcs7);
418 return "transaction ID of scep response does not match";
419 }
420 *data = pkcs7;
421 return NULL;
422 }