certificate: switch from Anon to WinNegotiate port
[siplcs.git] / src / core / sipe-certificate.c
blob0f0ad220dea4d43e112f1cf7d164678a851c2059
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 "sip-transport.h"
42 #include "sipe-backend.h"
43 #include "sipe-core.h"
44 #include "sipe-core-private.h"
45 #include "sipe-certificate.h"
46 #include "sipe-cert-crypto.h"
47 #include "sipe-digest.h"
48 #include "sipe-nls.h"
49 #include "sipe-svc.h"
50 #include "sipe-tls.h"
51 #include "sipe-utils.h"
52 #include "sipe-xml.h"
54 struct sipe_certificate {
55 GHashTable *certificates;
56 struct sipe_cert_crypto *backend;
59 void sipe_certificate_free(struct sipe_core_private *sipe_private)
61 struct sipe_certificate *sc = sipe_private->certificate;
63 if (sc) {
64 g_hash_table_destroy(sc->certificates);
65 sipe_cert_crypto_free(sc->backend);
66 g_free(sc);
70 static gboolean sipe_certificate_init(struct sipe_core_private *sipe_private)
72 struct sipe_certificate *sc;
73 struct sipe_cert_crypto *ssc;
75 if (sipe_private->certificate)
76 return(TRUE);
78 ssc = sipe_cert_crypto_init();
79 if (!ssc) {
80 SIPE_DEBUG_ERROR_NOFORMAT("sipe_certificate_init: crypto backend init FAILED!");
81 return(FALSE);
84 sc = g_new0(struct sipe_certificate, 1);
85 sc->certificates = g_hash_table_new_full(g_str_hash, g_str_equal,
86 g_free,
87 sipe_cert_crypto_destroy);
88 sc->backend = ssc;
90 SIPE_DEBUG_INFO_NOFORMAT("sipe_certificate_init: DONE");
92 sipe_private->certificate = sc;
93 return(TRUE);
96 static gchar *create_certreq(struct sipe_core_private *sipe_private,
97 const gchar *subject)
99 gchar *base64;
101 if (!sipe_certificate_init(sipe_private))
102 return(NULL);
104 SIPE_DEBUG_INFO_NOFORMAT("create_req: generating new certificate request");
106 base64 = sipe_cert_crypto_request(sipe_private->certificate->backend,
107 subject);
108 if (base64) {
109 GString *format = g_string_new(NULL);
110 gsize count = strlen(base64);
111 const gchar *p = base64;
113 /* Base64 needs to be formated correctly */
114 #define CERTREQ_BASE64_LINE_LENGTH 76
115 while (count > 0) {
116 gsize chunk = count > CERTREQ_BASE64_LINE_LENGTH ?
117 CERTREQ_BASE64_LINE_LENGTH : count;
118 g_string_append_len(format, p, chunk);
119 if (chunk == CERTREQ_BASE64_LINE_LENGTH)
120 g_string_append(format, "\r\n");
121 count -= chunk;
122 p += chunk;
125 /* swap Base64 buffers */
126 g_free(base64);
127 base64 = format->str;
128 g_string_free(format, FALSE);
131 return(base64);
134 static void add_certificate(struct sipe_core_private *sipe_private,
135 const gchar *target,
136 gpointer certificate)
138 struct sipe_certificate *sc = sipe_private->certificate;
139 g_hash_table_insert(sc->certificates, g_strdup(target), certificate);
142 gpointer sipe_certificate_tls_dsk_find(struct sipe_core_private *sipe_private,
143 const gchar *target)
145 struct sipe_certificate *sc = sipe_private->certificate;
146 gpointer certificate;
148 if (!target || !sc)
149 return(NULL);
151 certificate = g_hash_table_lookup(sc->certificates, target);
153 /* Let's make sure the certificate is still valid for another hour */
154 if (!sipe_cert_crypto_valid(certificate, 60 * 60)) {
155 SIPE_DEBUG_ERROR("sipe_certificate_tls_dsk_find: certificate for '%s' is invalid",
156 target);
157 return(NULL);
160 return(certificate);
163 struct certificate_callback_data {
164 gchar *target;
165 gchar *authuser;
166 gchar *webticket_negotiate_uri;
167 gchar *webticket_fedbearer_uri;
168 gchar *certprov_uri;
170 gboolean tried_fedbearer;
171 gboolean webticket_for_certprov;
173 struct sipe_tls_random entropy;
176 static void callback_data_free(struct certificate_callback_data *ccd)
178 if (ccd) {
179 g_free(ccd->target);
180 g_free(ccd->authuser);
181 g_free(ccd->webticket_negotiate_uri);
182 g_free(ccd->webticket_fedbearer_uri);
183 g_free(ccd->certprov_uri);
184 sipe_tls_free_random(&ccd->entropy);
185 g_free(ccd);
189 static void certificate_failure(struct sipe_core_private *sipe_private,
190 const gchar *format,
191 const gchar *parameter)
193 gchar *tmp = g_strdup_printf(format, parameter);
194 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
195 SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
196 tmp);
197 g_free(tmp);
200 static void get_and_publish_cert(struct sipe_core_private *sipe_private,
201 const gchar *uri,
202 SIPE_UNUSED_PARAMETER const gchar *raw,
203 sipe_xml *soap_body,
204 gpointer callback_data)
206 struct certificate_callback_data *ccd = callback_data;
207 gboolean success = (uri == NULL); /* abort case */
209 if (soap_body) {
210 gchar *cert_base64 = sipe_xml_data(sipe_xml_child(soap_body,
211 "Body/GetAndPublishCertResponse/RequestSecurityTokenResponse/RequestedSecurityToken/BinarySecurityToken"));
213 SIPE_DEBUG_INFO("get_and_publish_cert: received valid SOAP message from service %s",
214 uri);
216 if (cert_base64) {
217 gpointer opaque = sipe_cert_crypto_decode(sipe_private->certificate->backend,
218 cert_base64);
220 SIPE_DEBUG_INFO_NOFORMAT("get_and_publish_cert: found certificate");
222 if (opaque) {
223 add_certificate(sipe_private,
224 ccd->target,
225 opaque);
226 SIPE_DEBUG_INFO("get_and_publish_cert: certificate for target '%s' added",
227 ccd->target);
229 /* Let's try this again... */
230 sip_transport_authentication_completed(sipe_private);
231 success = TRUE;
234 g_free(cert_base64);
239 if (!success) {
240 certificate_failure(sipe_private,
241 _("Certificate request to %s failed"),
242 uri);
245 callback_data_free(ccd);
248 static gchar *extract_raw_xml_attribute(const gchar *xml,
249 const gchar *name)
251 gchar *attr_start = g_strdup_printf("%s=\"", name);
252 gchar *data = NULL;
253 const gchar *start = strstr(xml, attr_start);
255 if (start) {
256 const gchar *value = start + strlen(attr_start);
257 const gchar *end = strchr(value, '"');
258 if (end) {
259 data = g_strndup(value, end - value);
263 g_free(attr_start);
264 return(data);
267 static gchar *extract_raw_xml(const gchar *xml,
268 const gchar *tag,
269 gboolean include_tag)
271 gchar *tag_start = g_strdup_printf("<%s", tag);
272 gchar *tag_end = g_strdup_printf("</%s>", tag);
273 gchar *data = NULL;
274 const gchar *start = strstr(xml, tag_start);
276 if (start) {
277 const gchar *end = strstr(start + strlen(tag_start), tag_end);
278 if (end) {
279 if (include_tag) {
280 data = g_strndup(start, end + strlen(tag_end) - start);
281 } else {
282 const gchar *tmp = strchr(start + strlen(tag_start), '>') + 1;
283 data = g_strndup(tmp, end - tmp);
288 g_free(tag_end);
289 g_free(tag_start);
290 return(data);
293 static gchar *generate_timestamp(const gchar *raw,
294 const gchar *lifetime_tag)
296 gchar *lifetime = extract_raw_xml(raw, lifetime_tag, FALSE);
297 gchar *timestamp = NULL;
298 if (lifetime)
299 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>",
300 lifetime);
301 g_free(lifetime);
302 return(timestamp);
305 static gchar *generate_fedbearer_wsse(const gchar *raw)
307 gchar *timestamp = generate_timestamp(raw, "wst:Lifetime");
308 gchar *keydata = extract_raw_xml(raw, "EncryptedData", TRUE);
309 gchar *wsse_security = NULL;
311 if (timestamp && keydata) {
312 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
313 wsse_security = g_strconcat(timestamp, keydata, NULL);
316 g_free(keydata);
317 g_free(timestamp);
318 return(wsse_security);
321 static gchar *generate_sha1_proof_wsse(const gchar *raw,
322 struct sipe_tls_random *entropy)
324 gchar *timestamp = generate_timestamp(raw, "Lifetime");
325 gchar *keydata = extract_raw_xml(raw, "saml:Assertion", TRUE);
326 gchar *wsse_security = NULL;
328 if (timestamp && keydata) {
329 gchar *assertionID = extract_raw_xml_attribute(keydata,
330 "AssertionID");
333 * WS-Trust 1.3
335 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
337 * "The key is computed using P_SHA1() from the TLS sepcification to generate
338 * a bit stream using entropy from both sides. The exact form is:
340 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
342 gchar *entropy_res_base64 = extract_raw_xml(raw, "BinarySecret", FALSE);
343 gsize entropy_res_length;
344 guchar *entropy_response = g_base64_decode(entropy_res_base64,
345 &entropy_res_length);
346 guchar *key = sipe_tls_p_sha1(entropy->buffer,
347 entropy->length,
348 entropy_response,
349 entropy_res_length,
350 entropy->length);
351 g_free(entropy_response);
352 g_free(entropy_res_base64);
354 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
356 if (assertionID && key) {
357 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
358 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
359 gchar *base64;
360 gchar *signed_info;
361 gchar *canon;
363 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
365 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
366 sipe_digest_sha1((guchar *) timestamp,
367 strlen(timestamp),
368 digest);
369 base64 = g_base64_encode(digest,
370 SIPE_DIGEST_SHA1_LENGTH);
372 /* XML-Sig: SignedInfo for reference element */
373 signed_info = g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
374 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
375 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
376 "<Reference URI=\"#timestamp\">"
377 "<Transforms>"
378 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
379 "</Transforms>"
380 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
381 "<DigestValue>%s</DigestValue>"
382 "</Reference>"
383 "</SignedInfo>",
384 base64);
385 g_free(base64);
387 /* XML-Sig: SignedInfo in canonical form */
388 canon = sipe_xml_exc_c14n(signed_info);
389 g_free(signed_info);
391 if (canon) {
392 gchar *signature;
394 /* calculate signature */
395 sipe_digest_hmac_sha1(key, entropy->length,
396 (guchar *)canon,
397 strlen(canon),
398 digest);
399 base64 = g_base64_encode(digest,
400 SIPE_DIGEST_HMAC_SHA1_LENGTH);
402 /* XML-Sig: Signature from SignedInfo + Key */
403 signature = g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
404 " %s"
405 " <SignatureValue>%s</SignatureValue>"
406 " <KeyInfo>"
407 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
408 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
409 " </wsse:SecurityTokenReference>"
410 " </KeyInfo>"
411 "</Signature>",
412 canon,
413 base64,
414 assertionID);
415 g_free(base64);
416 g_free(canon);
418 wsse_security = g_strconcat(timestamp,
419 keydata,
420 signature,
421 NULL);
422 g_free(signature);
427 g_free(key);
428 g_free(assertionID);
431 g_free(keydata);
432 g_free(timestamp);
433 return(wsse_security);
436 static void webticket_token(struct sipe_core_private *sipe_private,
437 const gchar *uri,
438 const gchar *raw,
439 sipe_xml *soap_body,
440 gpointer callback_data)
442 struct certificate_callback_data *ccd = callback_data;
443 gboolean success = (uri == NULL); /* abort case */
445 if (soap_body) {
446 /* WebTicket for Certificate Provisioning Service */
447 if (ccd->webticket_for_certprov) {
448 gchar *wsse_security = generate_sha1_proof_wsse(raw,
449 &ccd->entropy);
451 if (wsse_security) {
452 gchar *certreq_base64 = create_certreq(sipe_private,
453 ccd->authuser);
455 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
456 uri);
458 if (certreq_base64) {
460 SIPE_DEBUG_INFO_NOFORMAT("webticket_token: created certificate request");
462 success = sipe_svc_get_and_publish_cert(sipe_private,
463 ccd->certprov_uri,
464 ccd->authuser,
465 wsse_security,
466 certreq_base64,
467 get_and_publish_cert,
468 ccd);
469 if (success) {
470 /* callback data passed down the line */
471 ccd = NULL;
473 g_free(certreq_base64);
475 g_free(wsse_security);
478 /* WebTicket for federated authentication */
479 } else {
480 gchar *wsse_security = generate_fedbearer_wsse(raw);
482 if (wsse_security) {
484 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
485 uri);
487 success = sipe_svc_webticket(sipe_private,
488 ccd->webticket_fedbearer_uri,
489 ccd->authuser,
490 wsse_security,
491 ccd->certprov_uri,
492 &ccd->entropy,
493 webticket_token,
494 ccd);
495 ccd->webticket_for_certprov = TRUE;
497 if (success) {
498 /* callback data passed down the line */
499 ccd = NULL;
501 g_free(wsse_security);
505 } else if (uri) {
506 /* Retry with federated authentication? */
507 success = ccd->webticket_fedbearer_uri && !ccd->tried_fedbearer;
508 if (success) {
509 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
510 uri);
512 ccd->tried_fedbearer = TRUE;
513 success = sipe_svc_webticket_lmc(sipe_private,
514 ccd->authuser,
515 ccd->webticket_fedbearer_uri,
516 webticket_token,
517 ccd);
518 ccd->webticket_for_certprov = FALSE;
520 if (success) {
521 /* callback data passed down the line */
522 ccd = NULL;
527 if (!success) {
528 certificate_failure(sipe_private,
529 _("Web ticket request to %s failed"),
530 uri);
533 callback_data_free(ccd);
536 static void webticket_metadata(struct sipe_core_private *sipe_private,
537 const gchar *uri,
538 SIPE_UNUSED_PARAMETER const gchar *raw,
539 sipe_xml *metadata,
540 gpointer callback_data)
542 struct certificate_callback_data *ccd = callback_data;
544 if (metadata) {
545 const sipe_xml *node;
547 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
548 uri);
550 /* Authentication ports accepted by WebTicket Service */
551 for (node = sipe_xml_child(metadata, "service/port");
552 node;
553 node = sipe_xml_twin(node)) {
554 const gchar *auth_uri = sipe_xml_attribute(sipe_xml_child(node,
555 "address"),
556 "location");
558 if (auth_uri) {
559 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
560 "WebTicketServiceWinNegotiate")) {
561 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri);
562 g_free(ccd->webticket_negotiate_uri);
563 ccd->webticket_negotiate_uri = g_strdup(auth_uri);
564 } else if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
565 "WsFedBearer")) {
566 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri);
567 g_free(ccd->webticket_fedbearer_uri);
568 ccd->webticket_fedbearer_uri = g_strdup(auth_uri);
573 if (ccd->webticket_negotiate_uri || ccd->webticket_fedbearer_uri) {
574 gboolean success;
576 /* Entropy: 256 random bits */
577 if (!ccd->entropy.buffer)
578 sipe_tls_fill_random(&ccd->entropy, 256);
580 if (ccd->webticket_negotiate_uri) {
581 /* Try Negotiate authentication first */
583 success = sipe_svc_webticket(sipe_private,
584 ccd->webticket_negotiate_uri,
585 ccd->authuser,
586 NULL,
587 ccd->certprov_uri,
588 &ccd->entropy,
589 webticket_token,
590 ccd);
591 ccd->webticket_for_certprov = TRUE;
592 } else {
593 ccd->tried_fedbearer = TRUE;
594 success = sipe_svc_webticket_lmc(sipe_private,
595 ccd->authuser,
596 ccd->webticket_fedbearer_uri,
597 webticket_token,
598 ccd);
599 ccd->webticket_for_certprov = FALSE;
602 if (success) {
603 /* callback data passed down the line */
604 ccd = NULL;
605 } else {
606 certificate_failure(sipe_private,
607 _("Can't request security token from %s"),
608 ccd->webticket_negotiate_uri ? ccd->webticket_negotiate_uri : ccd->webticket_fedbearer_uri);
611 } else {
612 certificate_failure(sipe_private,
613 _("Can't find the authentication port for TLS-DSK web ticket URI %s"),
614 uri);
617 } else if (uri) {
618 certificate_failure(sipe_private,
619 _("Can't retrieve metadata for TLS-DSK web ticket URI %s"),
620 uri);
623 callback_data_free(ccd);
626 static void certprov_metadata(struct sipe_core_private *sipe_private,
627 const gchar *uri,
628 SIPE_UNUSED_PARAMETER const gchar *raw,
629 sipe_xml *metadata,
630 gpointer callback_data)
632 struct certificate_callback_data *ccd = callback_data;
634 if (metadata) {
635 const sipe_xml *node;
636 gchar *ticket_uri = NULL;
638 SIPE_DEBUG_INFO("certprov_metadata: metadata for service %s retrieved successfully",
639 uri);
641 /* WebTicket policies accepted by Certificate Provisioning Service */
642 for (node = sipe_xml_child(metadata, "Policy");
643 node;
644 node = sipe_xml_twin(node)) {
645 if (sipe_strcase_equal(sipe_xml_attribute(node, "Id"),
646 "CertProvisioningServiceWebTicketProof_SHA1_policy")) {
648 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: WebTicket policy found");
650 ticket_uri = sipe_xml_data(sipe_xml_child(node,
651 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
652 if (ticket_uri) {
653 SIPE_DEBUG_INFO("certprov_metadata: WebTicket URI %s", ticket_uri);
654 } else {
655 certificate_failure(sipe_private,
656 _("Can't find the web ticket URI for TLS-DSK certificate provisioning URI %s"),
657 uri);
659 break;
663 if (ticket_uri) {
665 /* Authentication ports accepted by Certificate Provisioning Service */
666 for (node = sipe_xml_child(metadata, "service/port");
667 node;
668 node = sipe_xml_twin(node)) {
669 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
670 "CertProvisioningServiceWebTicketProof_SHA1")) {
671 const gchar *auth_uri;
673 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: authentication port found");
675 auth_uri = sipe_xml_attribute(sipe_xml_child(node,
676 "address"),
677 "location");
678 if (auth_uri) {
679 SIPE_DEBUG_INFO("certprov_metadata: CertProv Auth URI %s", auth_uri);
681 if (sipe_svc_metadata(sipe_private,
682 ticket_uri,
683 webticket_metadata,
684 ccd)) {
685 /* Remember for later */
686 ccd->certprov_uri = g_strdup(auth_uri);
688 /* callback data passed down the line */
689 ccd = NULL;
690 } else {
691 certificate_failure(sipe_private,
692 _("Can't request metadata from %s"),
693 ticket_uri);
696 break;
700 g_free(ticket_uri);
702 if (!node) {
703 certificate_failure(sipe_private,
704 _("Can't find the authentication port for TLS-DSK certificate provisioning URI %s"),
705 uri);
708 } else {
709 certificate_failure(sipe_private,
710 _("Can't find the web ticket policy for TLS-DSK certificate provisioning URI %s"),
711 uri);
714 } else if (uri) {
715 certificate_failure(sipe_private,
716 _("Can't retrieve metadata for TLS-DSK certificate provisioning URI %s"),
717 uri);
720 callback_data_free(ccd);
723 gboolean sipe_certificate_tls_dsk_generate(struct sipe_core_private *sipe_private,
724 const gchar *target,
725 const gchar *authuser,
726 const gchar *uri)
728 struct certificate_callback_data *ccd = g_new0(struct certificate_callback_data, 1);
729 gboolean ret;
731 ccd->target = g_strdup(target);
732 ccd->authuser = g_strdup(authuser);
734 ret = sipe_svc_metadata(sipe_private, uri, certprov_metadata, ccd);
735 if (!ret)
736 callback_data_free(ccd);
738 return(ret);
742 Local Variables:
743 mode: c
744 c-file-style: "bsd"
745 indent-tabs-mode: t
746 tab-width: 8
747 End: