2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2005 Jan Hutter, Martin Willi
4 * Hochschule fuer Technik Rapperswil
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>.
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
21 #include <utils/debug.h>
22 #include <asn1/asn1.h>
23 #include <asn1/asn1_parser.h>
25 #include <crypto/pkcs9.h>
26 #include <crypto/rngs/rng.h>
27 #include <crypto/hashers/hasher.h>
31 static const char *pkiStatus_values
[] = { "0", "2", "3" };
33 static const char *pkiStatus_names
[] = {
40 static const char *msgType_values
[] = { "3", "19", "20", "21", "22" };
42 static const char *msgType_names
[] = {
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"
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 */
69 * Extract X.501 attributes
71 void extract_attributes(pkcs7_t
*pkcs7
, enumerator_t
*enumerator
,
72 scep_attributes_t
*attrs
)
76 if (pkcs7
->get_attribute(pkcs7
, OID_PKI_MESSAGE_TYPE
, enumerator
, &attr
))
80 for (m
= SCEP_CertRep_MSG
; m
< SCEP_Unknown_MSG
; m
++)
82 if (strncmp(msgType_values
[m
], attr
.ptr
, attr
.len
) == 0)
87 DBG2(DBG_APP
, "messageType: %s", msgType_names
[attrs
->msgType
]);
90 if (pkcs7
->get_attribute(pkcs7
, OID_PKI_STATUS
, enumerator
, &attr
))
94 for (s
= SCEP_SUCCESS
; s
< SCEP_UNKNOWN
; s
++)
96 if (strncmp(pkiStatus_values
[s
], attr
.ptr
, attr
.len
) == 0)
101 DBG2(DBG_APP
, "pkiStatus: %s", pkiStatus_names
[attrs
->pkiStatus
]);
104 if (pkcs7
->get_attribute(pkcs7
, OID_PKI_FAIL_INFO
, enumerator
, &attr
))
106 if (attr
.len
== 1 && *attr
.ptr
>= '0' && *attr
.ptr
<= '4')
108 attrs
->failInfo
= (failInfo_t
)(*attr
.ptr
- '0');
110 if (attrs
->failInfo
!= SCEP_unknown_REASON
)
112 DBG1(DBG_APP
, "failInfo: %s", failInfo_reasons
[attrs
->failInfo
]);
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
,
126 * Generates a unique fingerprint of the pkcs10 request
127 * by computing an MD5 hash over it
129 chunk_t
scep_generate_pkcs10_fingerprint(chunk_t pkcs10
)
131 chunk_t digest
= chunk_alloca(HASH_SIZE_MD5
);
134 hasher
= lib
->crypto
->create_hasher(lib
->crypto
, HASH_MD5
);
135 if (!hasher
|| !hasher
->get_hash(hasher
, pkcs10
, digest
.ptr
))
140 hasher
->destroy(hasher
);
142 return chunk_to_hex(digest
, NULL
, FALSE
);
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
149 void scep_generate_transaction_id(public_key_t
*key
, chunk_t
*transID
,
150 chunk_t
*serialNumber
)
152 chunk_t digest
= chunk_alloca(HASH_SIZE_MD5
);
153 chunk_t keyEncoding
= chunk_empty
, keyInfo
;
158 key
->get_encoding(key
, PUBKEY_ASN1_DER
, &keyEncoding
);
160 keyInfo
= asn1_wrap(ASN1_SEQUENCE
, "mm",
161 asn1_algorithmIdentifier(OID_RSA_ENCRYPTION
),
162 asn1_bitstring("m", keyEncoding
));
164 hasher
= lib
->crypto
->create_hasher(lib
->crypto
, HASH_MD5
);
165 if (!hasher
|| !hasher
->get_hash(hasher
, keyInfo
, digest
.ptr
))
167 memset(digest
.ptr
, 0, digest
.len
);
172 /* is the most significant bit of the digest set? */
173 msb_set
= (*digest
.ptr
& 0x80) == 0x80;
175 /* allocate space for the serialNumber */
176 serialNumber
->len
= msb_set
+ digest
.len
;
177 serialNumber
->ptr
= malloc(serialNumber
->len
);
179 /* the serial number as the two's complement of the digest */
180 pos
= serialNumber
->ptr
;
185 memcpy(pos
, digest
.ptr
, digest
.len
);
187 /* the transaction id is the serial number in hex format */
188 *transID
= chunk_to_hex(digest
, NULL
, TRUE
);
192 * Builds a pkcs7 enveloped and signed scep request
194 chunk_t
scep_build_request(chunk_t data
, chunk_t transID
, scep_msg_t msg
,
195 certificate_t
*enc_cert
, encryption_algorithm_t enc_alg
,
196 size_t key_size
, certificate_t
*signer_cert
,
197 hash_algorithm_t digest_alg
, private_key_t
*private_key
)
200 container_t
*container
;
203 chunk_t senderNonce
, msgType
;
205 /* generate senderNonce */
206 rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
);
207 if (!rng
|| !rng
->get_bytes(rng
, sizeof(nonce
), nonce
))
214 /* encrypt data in enveloped-data PKCS#7 */
215 container
= lib
->creds
->create(lib
->creds
,
216 CRED_CONTAINER
, CONTAINER_PKCS7_ENVELOPED_DATA
,
218 BUILD_CERT
, enc_cert
,
219 BUILD_ENCRYPTION_ALG
, enc_alg
,
220 BUILD_KEY_SIZE
, (int)key_size
,
226 if (!container
->get_encoding(container
, &request
))
228 container
->destroy(container
);
231 container
->destroy(container
);
233 /* sign enveloped-data in a signed-data PKCS#7 */
234 senderNonce
= asn1_wrap(ASN1_OCTET_STRING
, "c", chunk_from_thing(nonce
));
235 transID
= asn1_wrap(ASN1_PRINTABLESTRING
, "c", transID
);
236 msgType
= asn1_wrap(ASN1_PRINTABLESTRING
, "c",
237 chunk_create((char*)msgType_values
[msg
],
238 strlen(msgType_values
[msg
])));
240 container
= lib
->creds
->create(lib
->creds
,
241 CRED_CONTAINER
, CONTAINER_PKCS7_SIGNED_DATA
,
243 BUILD_SIGNING_CERT
, signer_cert
,
244 BUILD_SIGNING_KEY
, private_key
,
245 BUILD_DIGEST_ALG
, digest_alg
,
246 BUILD_PKCS7_ATTRIBUTE
, OID_PKI_SENDER_NONCE
, senderNonce
,
247 BUILD_PKCS7_ATTRIBUTE
, OID_PKI_TRANS_ID
, transID
,
248 BUILD_PKCS7_ATTRIBUTE
, OID_PKI_MESSAGE_TYPE
, msgType
,
252 free(senderNonce
.ptr
);
260 if (!container
->get_encoding(container
, &request
))
262 container
->destroy(container
);
265 container
->destroy(container
);
271 * Converts a binary request to base64 with 64 characters per line
272 * newline and '+' characters are escaped by %0A and %2B, respectively
274 static char* escape_http_request(chunk_t req
)
276 char *escaped_req
= NULL
;
282 /* compute and allocate the size of the base64-encoded request */
283 int len
= 1 + 4 * ((req
.len
+ 2) / 3);
284 char *encoded_req
= malloc(len
);
286 /* do the base64 conversion */
287 chunk_t base64
= chunk_to_base64(req
, encoded_req
);
288 len
= base64
.len
+ 1;
290 /* compute newline characters to be inserted every 64 characters */
291 lines
= (len
- 2) / 64;
293 /* count number of + characters to be escaped */
303 escaped_req
= malloc(len
+ 3 * (lines
+ plus
));
305 /* escape special characters in the request */
312 memcpy(p2
, "%0A", 3);
318 memcpy(p2
, "%2B", 3);
334 * Send a SCEP request via HTTP and wait for a response
336 bool scep_http_request(const char *url
, chunk_t msg
, scep_op_t op
,
337 bool http_get_request
, chunk_t
*response
)
341 char *complete_url
= NULL
;
343 /* initialize response */
344 *response
= chunk_empty
;
346 DBG2(DBG_APP
, "sending scep request to '%s'", url
);
348 if (op
== SCEP_PKI_OPERATION
)
350 const char operation
[] = "PKIOperation";
352 if (http_get_request
)
354 char *escaped_req
= escape_http_request(msg
);
356 /* form complete url */
357 len
= strlen(url
) + 20 + strlen(operation
) + strlen(escaped_req
) + 1;
358 complete_url
= malloc(len
);
359 snprintf(complete_url
, len
, "%s?operation=%s&message=%s"
360 , url
, operation
, escaped_req
);
363 status
= lib
->fetcher
->fetch(lib
->fetcher
, complete_url
, response
,
364 FETCH_HTTP_VERSION_1_0
,
365 FETCH_REQUEST_HEADER
, "Pragma:",
366 FETCH_REQUEST_HEADER
, "Host:",
367 FETCH_REQUEST_HEADER
, "Accept:",
372 /* form complete url */
373 len
= strlen(url
) + 11 + strlen(operation
) + 1;
374 complete_url
= malloc(len
);
375 snprintf(complete_url
, len
, "%s?operation=%s", url
, operation
);
377 status
= lib
->fetcher
->fetch(lib
->fetcher
, complete_url
, response
,
378 FETCH_HTTP_VERSION_1_0
,
379 FETCH_REQUEST_DATA
, msg
,
380 FETCH_REQUEST_TYPE
, "",
381 FETCH_REQUEST_HEADER
, "Expect:",
385 else /* SCEP_GET_CA_CERT */
387 const char operation
[] = "GetCACert";
390 /* escape spaces, TODO: complete URL escape */
391 for (i
= 0; i
< msg
.len
; i
++)
393 if (msg
.ptr
[i
] == ' ')
399 /* form complete url */
400 len
= strlen(url
) + 32 + strlen(operation
) + msg
.len
+ 1;
401 complete_url
= malloc(len
);
402 snprintf(complete_url
, len
, "%s?operation=%s&message=%.*s",
403 url
, operation
, (int)msg
.len
, msg
.ptr
);
405 status
= lib
->fetcher
->fetch(lib
->fetcher
, complete_url
, response
,
406 FETCH_HTTP_VERSION_1_0
,
411 return (status
== SUCCESS
);
414 err_t
scep_parse_response(chunk_t response
, chunk_t transID
,
415 container_t
**out
, scep_attributes_t
*attrs
)
417 enumerator_t
*enumerator
;
418 bool verified
= FALSE
;
419 container_t
*container
;
422 container
= lib
->creds
->create(lib
->creds
, CRED_CONTAINER
, CONTAINER_PKCS7
,
423 BUILD_BLOB_ASN1_DER
, response
, BUILD_END
);
426 return "error parsing the scep response";
428 if (container
->get_type(container
) != CONTAINER_PKCS7_SIGNED_DATA
)
430 container
->destroy(container
);
431 return "scep response is not PKCS#7 signed-data";
434 enumerator
= container
->create_signature_enumerator(container
);
435 while (enumerator
->enumerate(enumerator
, &auth
))
438 extract_attributes((pkcs7_t
*)container
, enumerator
, attrs
);
439 if (!chunk_equals(transID
, attrs
->transID
))
441 enumerator
->destroy(enumerator
);
442 container
->destroy(container
);
443 return "transaction ID of scep response does not match";
446 enumerator
->destroy(enumerator
);
449 container
->destroy(container
);
450 return "unable to verify PKCS#7 container";