2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2005 Jan Hutter, Martin Willi
5 * Copyright (C) secunet Security Networks AG
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>.
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
22 #include <utils/debug.h>
23 #include <asn1/asn1.h>
24 #include <asn1/asn1_parser.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
;
155 int zeros
= 0, msb_set
= 0;
157 key
->get_encoding(key
, PUBKEY_ASN1_DER
, &keyEncoding
);
159 keyInfo
= asn1_wrap(ASN1_SEQUENCE
, "mm",
160 asn1_algorithmIdentifier(OID_RSA_ENCRYPTION
),
161 asn1_bitstring("m", keyEncoding
));
163 hasher
= lib
->crypto
->create_hasher(lib
->crypto
, HASH_MD5
);
164 if (!hasher
|| !hasher
->get_hash(hasher
, keyInfo
, digest
.ptr
))
166 memset(digest
.ptr
, 0, digest
.len
);
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
)
175 if (digest
.ptr
[zeros
])
177 if (digest
.ptr
[zeros
] & 0x80)
185 *serialNumber
= chunk_alloc(digest
.len
- zeros
+ msb_set
);
188 serialNumber
->ptr
[0] = 0x00;
190 memcpy(serialNumber
->ptr
+ msb_set
, digest
.ptr
+ zeros
,
193 /* the transaction id is the serial number in hex format */
194 *transID
= chunk_to_hex(digest
, NULL
, TRUE
);
198 * Builds a pkcs7 enveloped and signed scep request
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
)
206 container_t
*container
;
209 chunk_t senderNonce
, msgType
;
211 /* generate senderNonce */
212 rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
);
213 if (!rng
|| !rng
->get_bytes(rng
, sizeof(nonce
), nonce
))
220 /* encrypt data in enveloped-data PKCS#7 */
221 container
= lib
->creds
->create(lib
->creds
,
222 CRED_CONTAINER
, CONTAINER_PKCS7_ENVELOPED_DATA
,
224 BUILD_CERT
, enc_cert
,
225 BUILD_ENCRYPTION_ALG
, enc_alg
,
226 BUILD_KEY_SIZE
, (int)key_size
,
232 if (!container
->get_encoding(container
, &request
))
234 container
->destroy(container
);
237 container
->destroy(container
);
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
])));
246 container
= lib
->creds
->create(lib
->creds
,
247 CRED_CONTAINER
, CONTAINER_PKCS7_SIGNED_DATA
,
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
,
258 free(senderNonce
.ptr
);
266 if (!container
->get_encoding(container
, &request
))
268 container
->destroy(container
);
271 container
->destroy(container
);
277 * Converts a binary request to base64 with 64 characters per line
278 * newline and '+' characters are escaped by %0A and %2B, respectively
280 static char* escape_http_request(chunk_t req
)
282 char *escaped_req
= NULL
;
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
);
292 /* do the base64 conversion */
293 chunk_t base64
= chunk_to_base64(req
, encoded_req
);
294 len
= base64
.len
+ 1;
296 /* compute newline characters to be inserted every 64 characters */
297 lines
= (len
- 2) / 64;
299 /* count number of + characters to be escaped */
309 escaped_req
= malloc(len
+ 3 * (lines
+ plus
));
311 /* escape special characters in the request */
318 memcpy(p2
, "%0A", 3);
324 memcpy(p2
, "%2B", 3);
340 * Send a SCEP request via HTTP and wait for a response
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
,
348 char *complete_url
= NULL
;
349 host_t
*srcip
= NULL
;
351 /* initialize response */
352 *response
= chunk_empty
;
356 srcip
= host_create_from_string(src
, 0);
359 DBG2(DBG_APP
, "sending scep request to '%s'", url
);
361 if (op
== SCEP_PKI_OPERATION
)
363 const char operation
[] = "PKIOperation";
365 if (http_get_request
)
367 char *escaped_req
= escape_http_request(msg
);
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
);
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
,
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
);
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
,
402 else /* SCEP_GET_CA_CERT */
404 const char operation
[] = "GetCACert";
407 /* escape spaces, TODO: complete URL escape */
408 for (i
= 0; i
< msg
.len
; i
++)
410 if (msg
.ptr
[i
] == ' ')
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
);
422 status
= lib
->fetcher
->fetch(lib
->fetcher
, complete_url
, response
,
423 FETCH_HTTP_VERSION_1_0
,
424 FETCH_TIMEOUT
, timeout
,
425 FETCH_SOURCEIP
, srcip
,
431 return (status
== SUCCESS
);
434 err_t
scep_parse_response(chunk_t response
, chunk_t transID
,
435 container_t
**out
, scep_attributes_t
*attrs
)
437 enumerator_t
*enumerator
;
438 bool verified
= FALSE
;
439 container_t
*container
;
442 container
= lib
->creds
->create(lib
->creds
, CRED_CONTAINER
, CONTAINER_PKCS7
,
443 BUILD_BLOB_ASN1_DER
, response
, BUILD_END
);
446 return "error parsing the scep response";
448 if (container
->get_type(container
) != CONTAINER_PKCS7_SIGNED_DATA
)
450 container
->destroy(container
);
451 return "scep response is not PKCS#7 signed-data";
454 enumerator
= container
->create_signature_enumerator(container
);
455 while (enumerator
->enumerate(enumerator
, &auth
))
458 extract_attributes((pkcs7_t
*)container
, enumerator
, attrs
);
459 if (!chunk_equals(transID
, attrs
->transID
))
461 enumerator
->destroy(enumerator
);
462 container
->destroy(container
);
463 return "transaction ID of scep response does not match";
466 enumerator
->destroy(enumerator
);
469 container
->destroy(container
);
470 return "unable to verify PKCS#7 container";