certificate: implement certificate store
[siplcs.git] / src / core / sipe-certificate.c
blob6dd8d1961e90ee9cff9b30533991b88b2739a29d
1 /**
2 * @file sipe-certificate.c
4 * pidgin-sipe
6 * Copyright (C) 2011 SIPE Project <http://sipe.sourceforge.net/>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * Specification references:
26 * - [MS-SIPAE]: http://msdn.microsoft.com/en-us/library/cc431510.aspx
27 * - [MS-OCAUTHWS]: http://msdn.microsoft.com/en-us/library/ff595592.aspx
28 * - MS Tech-Ed Europe 2010 "UNC310: Microsoft Lync 2010 Technology Explained"
29 * http://ecn.channel9.msdn.com/o9/te/Europe/2010/pptx/unc310.pptx
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
36 #include <string.h>
38 #include <glib.h>
40 #include "sipe-common.h"
41 #include "sipe-backend.h"
42 #include "sipe-core.h"
43 #include "sipe-core-private.h"
44 #include "sipe-certificate.h"
45 #include "sipe-cert-crypto.h"
46 #include "sipe-digest.h"
47 #include "sipe-nls.h"
48 #include "sipe-svc.h"
49 #include "sipe-utils.h"
50 #include "sipe-xml.h"
52 struct sipe_certificate {
53 GHashTable *certificates;
54 struct sipe_cert_crypto *backend;
57 void sipe_certificate_free(struct sipe_core_private *sipe_private)
59 struct sipe_certificate *sc = sipe_private->certificate;
61 if (sc) {
62 g_hash_table_destroy(sc->certificates);
63 sipe_cert_crypto_free(sc->backend);
64 g_free(sc);
68 static gboolean sipe_certificate_init(struct sipe_core_private *sipe_private)
70 struct sipe_certificate *sc;
71 struct sipe_cert_crypto *ssc;
73 if (sipe_private->certificate)
74 return(TRUE);
76 ssc = sipe_cert_crypto_init();
77 if (!ssc) {
78 SIPE_DEBUG_ERROR_NOFORMAT("sipe_certificate_init: crypto backend init FAILED!");
79 return(FALSE);
82 sc = g_new0(struct sipe_certificate, 1);
83 sc->certificates = g_hash_table_new_full(g_str_hash, g_str_equal,
84 g_free,
85 sipe_cert_crypto_destroy);
86 sc->backend = ssc;
88 SIPE_DEBUG_INFO_NOFORMAT("sipe_certificate_init: DONE");
90 sipe_private->certificate = sc;
91 return(TRUE);
94 static gchar *create_certreq(struct sipe_core_private *sipe_private,
95 const gchar *subject)
97 if (!sipe_certificate_init(sipe_private))
98 return(NULL);
100 return(sipe_cert_crypto_request(sipe_private->certificate->backend,
101 subject));
104 gpointer sipe_certificate_tls_dsk_find(struct sipe_core_private *sipe_private,
105 const gchar *target)
107 struct sipe_certificate *sc = sipe_private->certificate;
109 if (!target || !sc)
110 return(NULL);
112 return(g_hash_table_lookup(sc->certificates, target));
115 struct certificate_callback_data {
116 gchar *target;
117 gchar *authuser;
118 gchar *webticket_anon_uri;
119 gchar *webticket_fedbearer_uri;
120 gchar *certprov_uri;
122 gboolean tried_fedbearer;
123 gboolean webticket_for_certprov;
125 struct sipe_svc_random entropy;
128 static void callback_data_free(struct certificate_callback_data *ccd)
130 if (ccd) {
131 g_free(ccd->target);
132 g_free(ccd->authuser);
133 g_free(ccd->webticket_anon_uri);
134 g_free(ccd->webticket_fedbearer_uri);
135 g_free(ccd->certprov_uri);
136 sipe_svc_free_random(&ccd->entropy);
137 g_free(ccd);
141 static void certificate_failure(struct sipe_core_private *sipe_private,
142 const gchar *format,
143 const gchar *parameter)
145 gchar *tmp = g_strdup_printf(format, parameter);
146 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
147 SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
148 tmp);
149 g_free(tmp);
152 static void get_and_publish_cert(struct sipe_core_private *sipe_private,
153 const gchar *uri,
154 const gchar *raw,
155 sipe_xml *soap_body,
156 gpointer callback_data)
158 struct certificate_callback_data *ccd = callback_data;
160 if (soap_body) {
162 SIPE_DEBUG_INFO("get_and_publish_cert: received valid SOAP message from service %s",
163 uri);
165 /* TBD.... */
166 (void)raw;
168 } else if (uri) {
169 certificate_failure(sipe_private,
170 _("Certitifcate request to %s failed"),
171 uri);
174 callback_data_free(ccd);
177 static gchar *extract_raw_xml_attribute(const gchar *xml,
178 const gchar *name)
180 gchar *attr_start = g_strdup_printf("%s=\"", name);
181 gchar *data = NULL;
182 const gchar *start = strstr(xml, attr_start);
184 if (start) {
185 const gchar *value = start + strlen(attr_start);
186 const gchar *end = strchr(value, '"');
187 if (end) {
188 data = g_strndup(value, end - value);
192 g_free(attr_start);
193 return(data);
196 static gchar *extract_raw_xml(const gchar *xml,
197 const gchar *tag,
198 gboolean include_tag)
200 gchar *tag_start = g_strdup_printf("<%s", tag);
201 gchar *tag_end = g_strdup_printf("</%s>", tag);
202 gchar *data = NULL;
203 const gchar *start = strstr(xml, tag_start);
205 if (start) {
206 const gchar *end = strstr(start + strlen(tag_start), tag_end);
207 if (end) {
208 if (include_tag) {
209 data = g_strndup(start, end + strlen(tag_end) - start);
210 } else {
211 const gchar *tmp = strchr(start + strlen(tag_start), '>') + 1;
212 data = g_strndup(tmp, end - tmp);
217 g_free(tag_end);
218 g_free(tag_start);
219 return(data);
222 static gchar *generate_timestamp(const gchar *raw,
223 const gchar *lifetime_tag)
225 gchar *lifetime = extract_raw_xml(raw, lifetime_tag, FALSE);
226 gchar *timestamp = NULL;
227 if (lifetime)
228 timestamp = g_strdup_printf("<wsu:Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"timestamp\">%s</wsu:Timestamp>",
229 lifetime);
230 g_free(lifetime);
231 return(timestamp);
234 static gchar *generate_fedbearer_wsse(const gchar *raw)
236 gchar *timestamp = generate_timestamp(raw, "wst:Lifetime");
237 gchar *keydata = extract_raw_xml(raw, "EncryptedData", TRUE);
238 gchar *wsse_security = NULL;
240 if (timestamp && keydata) {
241 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
242 wsse_security = g_strconcat(timestamp, keydata, NULL);
245 g_free(keydata);
246 g_free(timestamp);
247 return(wsse_security);
250 /* P_SHA1() - see RFC2246 "The TLS Protocol Version 1.0", Section 5 */
251 static guchar *p_sha1(const guchar *secret,
252 gsize secret_length,
253 const guchar *seed,
254 gsize seed_length,
255 gsize output_length)
257 guchar *output = NULL;
260 * output_length == 0 -> illegal
261 * output_length == 1..20 -> iterations = 1
262 * output_length == 21..40 -> iterations = 2
264 if (secret && seed && (output_length > 0)) {
265 guint iterations = (output_length + SIPE_DIGEST_HMAC_SHA1_LENGTH - 1) / SIPE_DIGEST_HMAC_SHA1_LENGTH;
266 guchar *concat = g_malloc(SIPE_DIGEST_HMAC_SHA1_LENGTH + seed_length);
267 guchar A[SIPE_DIGEST_HMAC_SHA1_LENGTH];
268 guchar *p;
270 SIPE_DEBUG_INFO("p_sha1: secret %" G_GSIZE_FORMAT " bytes, seed %" G_GSIZE_FORMAT " bytes",
271 secret_length, seed_length);
272 SIPE_DEBUG_INFO("p_sha1: output %" G_GSIZE_FORMAT " bytes -> %d iterations",
273 output_length, iterations);
275 /* A(1) = HMAC_SHA1(secret, A(0)), A(0) = seed */
276 sipe_digest_hmac_sha1(secret, secret_length,
277 seed, seed_length,
280 /* Each iteration adds SIPE_DIGEST_HMAC_SHA1_LENGTH bytes */
281 p = output = g_malloc(iterations * SIPE_DIGEST_HMAC_SHA1_LENGTH);
283 while (iterations-- > 0) {
284 /* P_SHA1(i) = HMAC_SHA1(secret, A(i) + seed), i = 1, 2, ... */
285 guchar P[SIPE_DIGEST_HMAC_SHA1_LENGTH];
286 memcpy(concat, A, SIPE_DIGEST_HMAC_SHA1_LENGTH);
287 memcpy(concat + SIPE_DIGEST_HMAC_SHA1_LENGTH, seed, seed_length);
288 sipe_digest_hmac_sha1(secret, secret_length,
289 concat, SIPE_DIGEST_HMAC_SHA1_LENGTH + seed_length,
291 memcpy(p, P, SIPE_DIGEST_HMAC_SHA1_LENGTH);
292 p += SIPE_DIGEST_HMAC_SHA1_LENGTH;
294 /* A(i+1) = HMAC_SHA1(secret, A(i)) */
295 sipe_digest_hmac_sha1(secret, secret_length,
296 A, SIPE_DIGEST_HMAC_SHA1_LENGTH,
299 g_free(concat);
302 return(output);
305 static gchar *generate_sha1_proof_wsse(const gchar *raw,
306 struct sipe_svc_random *entropy)
308 gchar *timestamp = generate_timestamp(raw, "Lifetime");
309 gchar *keydata = extract_raw_xml(raw, "saml:Assertion", TRUE);
310 gchar *wsse_security = NULL;
312 if (timestamp && keydata) {
313 gchar *assertionID = extract_raw_xml_attribute(keydata,
314 "AssertionID");
317 * WS-Trust 1.3
319 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
321 * "The key is computed using P_SHA1() from the TLS sepcification to generate
322 * a bit stream using entropy from both sides. The exact form is:
324 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
326 gchar *entropy_res_base64 = extract_raw_xml(raw, "BinarySecret", FALSE);
327 gsize entropy_res_length;
328 guchar *entropy_response = g_base64_decode(entropy_res_base64,
329 &entropy_res_length);
330 guchar *key = p_sha1(entropy->buffer,
331 entropy->length,
332 entropy_response,
333 entropy_res_length,
334 entropy->length);
335 g_free(entropy_response);
336 g_free(entropy_res_base64);
338 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
340 if (assertionID && key) {
341 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
342 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
343 gchar *base64;
344 gchar *signed_info;
345 gchar *canon;
347 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
349 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
350 sipe_digest_sha1((guchar *) timestamp,
351 strlen(timestamp),
352 digest);
353 base64 = g_base64_encode(digest,
354 SIPE_DIGEST_SHA1_LENGTH);
356 /* XML-Sig: SignedInfo for reference element */
357 signed_info = g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
358 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
359 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
360 "<Reference URI=\"#timestamp\">"
361 "<Transforms>"
362 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
363 "</Transforms>"
364 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
365 "<DigestValue>%s</DigestValue>"
366 "</Reference>"
367 "</SignedInfo>",
368 base64);
369 g_free(base64);
371 /* XML-Sig: SignedInfo in canonical form */
372 canon = sipe_xml_exc_c14n(signed_info);
373 g_free(signed_info);
375 if (canon) {
376 gchar *signature;
378 /* calculate signature */
379 sipe_digest_hmac_sha1(key, entropy->length,
380 (guchar *)canon,
381 strlen(canon),
382 digest);
383 base64 = g_base64_encode(digest,
384 SIPE_DIGEST_HMAC_SHA1_LENGTH);
386 /* XML-Sig: Signature from SignedInfo + Key */
387 signature = g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
388 " %s"
389 " <SignatureValue>%s</SignatureValue>"
390 " <KeyInfo>"
391 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
392 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
393 " </wsse:SecurityTokenReference>"
394 " </KeyInfo>"
395 "</Signature>",
396 canon,
397 base64,
398 assertionID);
399 g_free(base64);
400 g_free(canon);
402 wsse_security = g_strconcat(timestamp,
403 keydata,
404 signature,
405 NULL);
406 g_free(signature);
411 g_free(key);
412 g_free(assertionID);
415 g_free(keydata);
416 g_free(timestamp);
417 return(wsse_security);
420 static void webticket_token(struct sipe_core_private *sipe_private,
421 const gchar *uri,
422 const gchar *raw,
423 sipe_xml *soap_body,
424 gpointer callback_data)
426 struct certificate_callback_data *ccd = callback_data;
427 gboolean success = (uri == NULL); /* abort case */
429 if (soap_body) {
430 /* WebTicket for Certificate Provisioning Service */
431 if (ccd->webticket_for_certprov) {
432 gchar *wsse_security = generate_sha1_proof_wsse(raw,
433 &ccd->entropy);
435 if (wsse_security) {
436 gchar *certreq_base64 = create_certreq(sipe_private,
437 ccd->authuser);
439 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
440 uri);
442 if (certreq_base64) {
444 SIPE_DEBUG_INFO_NOFORMAT("webticket_token: created certificate request");
446 success = sipe_svc_get_and_publish_cert(sipe_private,
447 ccd->certprov_uri,
448 ccd->authuser,
449 wsse_security,
450 certreq_base64,
451 get_and_publish_cert,
452 ccd);
453 if (success) {
454 /* callback data passed down the line */
455 ccd = NULL;
457 g_free(certreq_base64);
459 g_free(wsse_security);
462 /* WebTicket for federated authentication */
463 } else {
464 gchar *wsse_security = generate_fedbearer_wsse(raw);
466 if (wsse_security) {
468 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
469 uri);
471 success = sipe_svc_webticket(sipe_private,
472 ccd->webticket_fedbearer_uri,
473 ccd->authuser,
474 wsse_security,
475 ccd->certprov_uri,
476 &ccd->entropy,
477 webticket_token,
478 ccd);
479 ccd->webticket_for_certprov = TRUE;
481 if (success) {
482 /* callback data passed down the line */
483 ccd = NULL;
485 g_free(wsse_security);
489 } else if (uri) {
490 /* Retry with federated authentication? */
491 success = ccd->webticket_fedbearer_uri && !ccd->tried_fedbearer;
492 if (success) {
493 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
494 uri);
496 ccd->tried_fedbearer = TRUE;
497 success = sipe_svc_webticket_lmc(sipe_private,
498 ccd->authuser,
499 ccd->webticket_fedbearer_uri,
500 webticket_token,
501 ccd);
502 ccd->webticket_for_certprov = FALSE;
504 if (success) {
505 /* callback data passed down the line */
506 ccd = NULL;
511 if (!success) {
512 certificate_failure(sipe_private,
513 _("Web ticket request to %s failed"),
514 uri);
517 callback_data_free(ccd);
520 static void webticket_metadata(struct sipe_core_private *sipe_private,
521 const gchar *uri,
522 SIPE_UNUSED_PARAMETER const gchar *raw,
523 sipe_xml *metadata,
524 gpointer callback_data)
526 struct certificate_callback_data *ccd = callback_data;
528 if (metadata) {
529 const sipe_xml *node;
531 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
532 uri);
534 /* Authentication ports accepted by WebTicket Service */
535 for (node = sipe_xml_child(metadata, "service/port");
536 node;
537 node = sipe_xml_twin(node)) {
538 const gchar *auth_uri = sipe_xml_attribute(sipe_xml_child(node,
539 "address"),
540 "location");
542 if (auth_uri) {
543 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
544 "WebTicketServiceAnon")) {
545 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Anon Auth URI %s", auth_uri);
546 g_free(ccd->webticket_anon_uri);
547 ccd->webticket_anon_uri = g_strdup(auth_uri);
548 } else if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
549 "WsFedBearer")) {
550 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri);
551 g_free(ccd->webticket_fedbearer_uri);
552 ccd->webticket_fedbearer_uri = g_strdup(auth_uri);
557 if (ccd->webticket_anon_uri || ccd->webticket_fedbearer_uri) {
558 gboolean success;
560 if (ccd->webticket_anon_uri) {
561 /* Try anonymous authentication first */
562 /* Entropy: 256 random bits */
563 sipe_svc_fill_random(&ccd->entropy, 256);
565 success = sipe_svc_webticket(sipe_private,
566 ccd->webticket_anon_uri,
567 ccd->authuser,
568 NULL,
569 ccd->certprov_uri,
570 &ccd->entropy,
571 webticket_token,
572 ccd);
573 ccd->webticket_for_certprov = TRUE;
574 } else {
575 ccd->tried_fedbearer = TRUE;
576 success = sipe_svc_webticket_lmc(sipe_private,
577 ccd->authuser,
578 ccd->webticket_fedbearer_uri,
579 webticket_token,
580 ccd);
581 ccd->webticket_for_certprov = FALSE;
584 if (success) {
585 /* callback data passed down the line */
586 ccd = NULL;
587 } else {
588 certificate_failure(sipe_private,
589 _("Can't request security token from %s"),
590 ccd->webticket_anon_uri ? ccd->webticket_anon_uri : ccd->webticket_fedbearer_uri);
593 } else {
594 certificate_failure(sipe_private,
595 _("Can't find the authentication port for TLS-DSK web ticket URI %s"),
596 uri);
599 } else if (uri) {
600 certificate_failure(sipe_private,
601 _("Can't retrieve metadata for TLS-DSK web ticket URI %s"),
602 uri);
605 callback_data_free(ccd);
608 static void certprov_metadata(struct sipe_core_private *sipe_private,
609 const gchar *uri,
610 SIPE_UNUSED_PARAMETER const gchar *raw,
611 sipe_xml *metadata,
612 gpointer callback_data)
614 struct certificate_callback_data *ccd = callback_data;
616 if (metadata) {
617 const sipe_xml *node;
618 gchar *ticket_uri = NULL;
620 SIPE_DEBUG_INFO("certprov_metadata: metadata for service %s retrieved successfully",
621 uri);
623 /* WebTicket policies accepted by Certificate Provisioning Service */
624 for (node = sipe_xml_child(metadata, "Policy");
625 node;
626 node = sipe_xml_twin(node)) {
627 if (sipe_strcase_equal(sipe_xml_attribute(node, "Id"),
628 "CertProvisioningServiceWebTicketProof_SHA1_policy")) {
630 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: WebTicket policy found");
632 ticket_uri = sipe_xml_data(sipe_xml_child(node,
633 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
634 if (ticket_uri) {
635 SIPE_DEBUG_INFO("certprov_metadata: WebTicket URI %s", ticket_uri);
636 } else {
637 certificate_failure(sipe_private,
638 _("Can't find the WebTicket URI for TLS-DSK certificate provisioning URI %s"),
639 uri);
641 break;
645 if (ticket_uri) {
647 /* Authentication ports accepted by Certificate Provisioning Service */
648 for (node = sipe_xml_child(metadata, "service/port");
649 node;
650 node = sipe_xml_twin(node)) {
651 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
652 "CertProvisioningServiceWebTicketProof_SHA1")) {
653 const gchar *auth_uri;
655 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: authentication port found");
657 auth_uri = sipe_xml_attribute(sipe_xml_child(node,
658 "address"),
659 "location");
660 if (auth_uri) {
661 SIPE_DEBUG_INFO("certprov_metadata: CertProv Auth URI %s", auth_uri);
663 if (sipe_svc_metadata(sipe_private,
664 ticket_uri,
665 webticket_metadata,
666 ccd)) {
667 /* Remember for later */
668 ccd->certprov_uri = g_strdup(auth_uri);
670 /* callback data passed down the line */
671 ccd = NULL;
672 } else {
673 certificate_failure(sipe_private,
674 _("Can't request metadata from %s"),
675 ticket_uri);
678 break;
682 g_free(ticket_uri);
684 if (!node) {
685 certificate_failure(sipe_private,
686 _("Can't find the authentication port for TLS-DSK certificate provisioning URI %s"),
687 uri);
690 } else {
691 certificate_failure(sipe_private,
692 _("Can't find the WebTicket Policy for TLS-DSK certificate provisioning URI %s"),
693 uri);
696 } else if (uri) {
697 certificate_failure(sipe_private,
698 _("Can't retrieve metadata for TLS-DSK certificate provisioning URI %s"),
699 uri);
702 callback_data_free(ccd);
705 gboolean sipe_certificate_tls_dsk_generate(struct sipe_core_private *sipe_private,
706 const gchar *target,
707 const gchar *authuser,
708 const gchar *uri)
710 struct certificate_callback_data *ccd = g_new0(struct certificate_callback_data, 1);
711 gboolean ret;
713 ccd->target = g_strdup(target);
714 ccd->authuser = g_strdup(authuser);
716 ret = sipe_svc_metadata(sipe_private, uri, certprov_metadata, ccd);
717 if (!ret)
718 callback_data_free(ccd);
720 return(ret);
724 Local Variables:
725 mode: c
726 c-file-style: "bsd"
727 indent-tabs-mode: t
728 tab-width: 8
729 End: