webticket: request RealmInfo for WsFedBearer
[siplcs.git] / src / core / sipe-webticket.c
blob3a038d4fd112ef07bd666e944bc04d870e3c2c1a
1 /**
2 * @file sipe-webticket.c
4 * pidgin-sipe
6 * Copyright (C) 2011-12 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-OCAUTHWS]: http://msdn.microsoft.com/en-us/library/ff595592.aspx
27 * - MS Tech-Ed Europe 2010 "UNC310: Microsoft Lync 2010 Technology Explained"
28 * http://ecn.channel9.msdn.com/o9/te/Europe/2010/pptx/unc310.pptx
31 #include <string.h>
33 #include <glib.h>
35 #include "sipe-common.h"
36 #include "sipe-backend.h"
37 #include "sipe-core.h"
38 #include "sipe-core-private.h"
39 #include "sipe-digest.h"
40 #include "sipe-svc.h"
41 #include "sipe-tls.h"
42 #include "sipe-webticket.h"
43 #include "sipe-utils.h"
44 #include "sipe-xml.h"
46 struct webticket_callback_data {
47 gchar *service_uri;
48 const gchar *service_port;
49 gchar *service_auth_uri;
51 gchar *webticket_negotiate_uri;
52 gchar *webticket_fedbearer_uri;
54 gboolean tried_fedbearer;
55 gboolean webticket_for_service;
56 gboolean requires_signing;
58 struct sipe_tls_random entropy;
60 sipe_webticket_callback *callback;
61 gpointer callback_data;
63 struct sipe_svc_session *session;
66 static void callback_data_free(struct webticket_callback_data *wcd)
68 if (wcd) {
69 sipe_tls_free_random(&wcd->entropy);
70 g_free(wcd->webticket_negotiate_uri);
71 g_free(wcd->webticket_fedbearer_uri);
72 g_free(wcd->service_auth_uri);
73 g_free(wcd->service_uri);
74 g_free(wcd);
78 static gchar *extract_raw_xml_attribute(const gchar *xml,
79 const gchar *name)
81 gchar *attr_start = g_strdup_printf("%s=\"", name);
82 gchar *data = NULL;
83 const gchar *start = strstr(xml, attr_start);
85 if (start) {
86 const gchar *value = start + strlen(attr_start);
87 const gchar *end = strchr(value, '"');
88 if (end) {
89 data = g_strndup(value, end - value);
93 g_free(attr_start);
94 return(data);
97 static gchar *generate_timestamp(const gchar *raw,
98 const gchar *lifetime_tag)
100 gchar *lifetime = sipe_xml_extract_raw(raw, lifetime_tag, FALSE);
101 gchar *timestamp = NULL;
102 if (lifetime)
103 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>",
104 lifetime);
105 g_free(lifetime);
106 return(timestamp);
109 static gchar *generate_fedbearer_wsse(const gchar *raw)
111 gchar *timestamp = generate_timestamp(raw, "wst:Lifetime");
112 gchar *keydata = sipe_xml_extract_raw(raw, "EncryptedData", TRUE);
113 gchar *wsse_security = NULL;
115 if (timestamp && keydata) {
116 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
117 wsse_security = g_strconcat(timestamp, keydata, NULL);
120 g_free(keydata);
121 g_free(timestamp);
122 return(wsse_security);
125 static gchar *generate_sha1_proof_wsse(const gchar *raw,
126 struct sipe_tls_random *entropy)
128 gchar *timestamp = generate_timestamp(raw, "Lifetime");
129 gchar *keydata = sipe_xml_extract_raw(raw, "saml:Assertion", TRUE);
130 gchar *wsse_security = NULL;
132 if (timestamp && keydata) {
133 if (entropy) {
134 gchar *assertionID = extract_raw_xml_attribute(keydata,
135 "AssertionID");
138 * WS-Trust 1.3
140 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
142 * "The key is computed using P_SHA1() from the TLS sepcification to generate
143 * a bit stream using entropy from both sides. The exact form is:
145 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
147 gchar *entropy_res_base64 = sipe_xml_extract_raw(raw, "BinarySecret", FALSE);
148 gsize entropy_res_length;
149 guchar *entropy_response = g_base64_decode(entropy_res_base64,
150 &entropy_res_length);
151 guchar *key = sipe_tls_p_sha1(entropy->buffer,
152 entropy->length,
153 entropy_response,
154 entropy_res_length,
155 entropy->length);
156 g_free(entropy_response);
157 g_free(entropy_res_base64);
159 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
161 if (assertionID && key) {
162 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
163 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
164 gchar *base64;
165 gchar *signed_info;
166 gchar *canon;
168 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
170 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
171 sipe_digest_sha1((guchar *) timestamp,
172 strlen(timestamp),
173 digest);
174 base64 = g_base64_encode(digest,
175 SIPE_DIGEST_SHA1_LENGTH);
177 /* XML-Sig: SignedInfo for reference element */
178 signed_info = g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
179 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
180 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
181 "<Reference URI=\"#timestamp\">"
182 "<Transforms>"
183 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
184 "</Transforms>"
185 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
186 "<DigestValue>%s</DigestValue>"
187 "</Reference>"
188 "</SignedInfo>",
189 base64);
190 g_free(base64);
192 /* XML-Sig: SignedInfo in canonical form */
193 canon = sipe_xml_exc_c14n(signed_info);
194 g_free(signed_info);
196 if (canon) {
197 gchar *signature;
199 /* calculate signature */
200 sipe_digest_hmac_sha1(key, entropy->length,
201 (guchar *)canon,
202 strlen(canon),
203 digest);
204 base64 = g_base64_encode(digest,
205 SIPE_DIGEST_HMAC_SHA1_LENGTH);
207 /* XML-Sig: Signature from SignedInfo + Key */
208 signature = g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
209 " %s"
210 " <SignatureValue>%s</SignatureValue>"
211 " <KeyInfo>"
212 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
213 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
214 " </wsse:SecurityTokenReference>"
215 " </KeyInfo>"
216 "</Signature>",
217 canon,
218 base64,
219 assertionID);
220 g_free(base64);
221 g_free(canon);
223 wsse_security = g_strconcat(timestamp,
224 keydata,
225 signature,
226 NULL);
227 g_free(signature);
232 g_free(key);
233 g_free(assertionID);
234 } else {
235 /* token doesn't require signature */
236 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata, no signing required");
237 wsse_security = g_strconcat(timestamp,
238 keydata,
239 NULL);
243 g_free(keydata);
244 g_free(timestamp);
245 return(wsse_security);
248 static gboolean initiate_fedbearer(struct sipe_core_private *sipe_private,
249 struct webticket_callback_data *wcd);
250 static void webticket_token(struct sipe_core_private *sipe_private,
251 const gchar *uri,
252 const gchar *raw,
253 sipe_xml *soap_body,
254 gpointer callback_data)
256 struct webticket_callback_data *wcd = callback_data;
257 gboolean failed = TRUE;
259 if (soap_body) {
260 /* WebTicket for Web Service */
261 if (wcd->webticket_for_service) {
262 gchar *wsse_security = generate_sha1_proof_wsse(raw,
263 wcd->requires_signing ? &wcd->entropy : NULL);
265 if (wsse_security) {
266 /* callback takes ownership of wsse_security */
267 wcd->callback(sipe_private,
268 wcd->service_uri,
269 wcd->service_auth_uri,
270 wsse_security,
271 NULL,
272 wcd->callback_data);
273 failed = FALSE;
274 g_free(wsse_security);
277 /* WebTicket for federated authentication */
278 } else {
279 gchar *wsse_security = generate_fedbearer_wsse(raw);
281 if (wsse_security) {
283 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
284 uri);
286 if (sipe_svc_webticket(sipe_private,
287 wcd->session,
288 wcd->webticket_fedbearer_uri,
289 wsse_security,
290 wcd->service_auth_uri,
291 &wcd->entropy,
292 webticket_token,
293 wcd)) {
294 wcd->webticket_for_service = TRUE;
296 /* callback data passed down the line */
297 wcd = NULL;
299 g_free(wsse_security);
303 } else if (uri) {
304 /* Retry with federated authentication? */
305 if (wcd->webticket_fedbearer_uri && !wcd->tried_fedbearer) {
306 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
307 uri);
309 if (initiate_fedbearer(sipe_private, wcd)) {
310 /* callback data passed down the line */
311 wcd = NULL;
316 if (wcd) {
317 if (failed) {
318 gchar *failure_msg = NULL;
320 if (soap_body) {
321 failure_msg = sipe_xml_data(sipe_xml_child(soap_body,
322 "Body/Fault/Detail/error/internalerror/text"));
323 /* XML data can end in &#x000D;&#x000A; */
324 g_strstrip(failure_msg);
327 wcd->callback(sipe_private,
328 wcd->service_uri,
329 uri ? uri : NULL,
330 NULL,
331 failure_msg,
332 wcd->callback_data);
333 g_free(failure_msg);
335 callback_data_free(wcd);
339 static void realminfo(struct sipe_core_private *sipe_private,
340 SIPE_UNUSED_PARAMETER const gchar *uri,
341 SIPE_UNUSED_PARAMETER const gchar *raw,
342 sipe_xml *realminfo,
343 gpointer callback_data)
345 struct webticket_callback_data *wcd = callback_data;
347 if (realminfo) {
348 SIPE_DEBUG_INFO("realminfo: data for user %s retrieved successfully",
349 sipe_private->username);
351 if (sipe_svc_webticket_lmc(sipe_private,
352 wcd->session,
353 wcd->webticket_fedbearer_uri,
354 webticket_token,
355 wcd)) {
356 /* callback data passed down the line */
357 wcd = NULL;
361 if (wcd) {
362 wcd->callback(sipe_private,
363 wcd->service_uri,
364 uri ? uri : NULL,
365 NULL,
366 NULL,
367 wcd->callback_data);
368 callback_data_free(wcd);
372 static gboolean initiate_fedbearer(struct sipe_core_private *sipe_private,
373 struct webticket_callback_data *wcd)
375 gboolean success = sipe_svc_realminfo(sipe_private,
376 wcd->session,
377 realminfo,
378 wcd);
379 wcd->tried_fedbearer = TRUE;
380 wcd->webticket_for_service = FALSE;
382 return(success);
385 static void webticket_metadata(struct sipe_core_private *sipe_private,
386 const gchar *uri,
387 SIPE_UNUSED_PARAMETER const gchar *raw,
388 sipe_xml *metadata,
389 gpointer callback_data)
391 struct webticket_callback_data *wcd = callback_data;
393 if (metadata) {
394 const sipe_xml *node;
396 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
397 uri);
399 /* Authentication ports accepted by WebTicket Service */
400 for (node = sipe_xml_child(metadata, "service/port");
401 node;
402 node = sipe_xml_twin(node)) {
403 const gchar *auth_uri = sipe_xml_attribute(sipe_xml_child(node,
404 "address"),
405 "location");
407 if (auth_uri) {
408 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
409 "WebTicketServiceWinNegotiate")) {
410 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri);
411 g_free(wcd->webticket_negotiate_uri);
412 wcd->webticket_negotiate_uri = g_strdup(auth_uri);
413 } else if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
414 "WsFedBearer")) {
415 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri);
416 g_free(wcd->webticket_fedbearer_uri);
417 wcd->webticket_fedbearer_uri = g_strdup(auth_uri);
422 if (wcd->webticket_negotiate_uri || wcd->webticket_fedbearer_uri) {
423 gboolean success;
425 /* Entropy: 256 random bits */
426 if (!wcd->entropy.buffer)
427 sipe_tls_fill_random(&wcd->entropy, 256);
429 if (wcd->webticket_negotiate_uri) {
430 /* Try Negotiate authentication first */
432 success = sipe_svc_webticket(sipe_private,
433 wcd->session,
434 wcd->webticket_negotiate_uri,
435 NULL,
436 wcd->service_auth_uri,
437 &wcd->entropy,
438 webticket_token,
439 wcd);
440 wcd->webticket_for_service = TRUE;
441 } else {
442 success = initiate_fedbearer(sipe_private,
443 wcd);
446 if (success) {
447 /* callback data passed down the line */
448 wcd = NULL;
453 if (wcd) {
454 wcd->callback(sipe_private,
455 wcd->service_uri,
456 uri ? uri : NULL,
457 NULL,
458 NULL,
459 wcd->callback_data);
460 callback_data_free(wcd);
464 static void service_metadata(struct sipe_core_private *sipe_private,
465 const gchar *uri,
466 SIPE_UNUSED_PARAMETER const gchar *raw,
467 sipe_xml *metadata,
468 gpointer callback_data)
470 struct webticket_callback_data *wcd = callback_data;
472 if (metadata) {
473 const sipe_xml *node;
474 gchar *policy = g_strdup_printf("%s_policy", wcd->service_port);
475 gchar *ticket_uri = NULL;
477 SIPE_DEBUG_INFO("webservice_metadata: metadata for service %s retrieved successfully",
478 uri);
480 /* WebTicket policies accepted by Web Service */
481 for (node = sipe_xml_child(metadata, "Policy");
482 node;
483 node = sipe_xml_twin(node)) {
484 if (sipe_strcase_equal(sipe_xml_attribute(node, "Id"),
485 policy)) {
487 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: WebTicket policy found");
489 ticket_uri = sipe_xml_data(sipe_xml_child(node,
490 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
491 if (ticket_uri) {
492 /* this token type requires signing */
493 wcd->requires_signing = TRUE;
494 } else {
495 /* try alternative token type */
496 ticket_uri = sipe_xml_data(sipe_xml_child(node,
497 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
499 if (ticket_uri) {
500 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri);
502 break;
505 g_free(policy);
507 if (ticket_uri) {
509 /* Authentication ports accepted by Web Service */
510 for (node = sipe_xml_child(metadata, "service/port");
511 node;
512 node = sipe_xml_twin(node)) {
513 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
514 wcd->service_port)) {
515 const gchar *auth_uri;
517 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: authentication port found");
519 auth_uri = sipe_xml_attribute(sipe_xml_child(node,
520 "address"),
521 "location");
522 if (auth_uri) {
523 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri);
525 if (sipe_svc_metadata(sipe_private,
526 wcd->session,
527 ticket_uri,
528 webticket_metadata,
529 wcd)) {
530 /* Remember for later */
531 wcd->service_auth_uri = g_strdup(auth_uri);
533 /* callback data passed down the line */
534 wcd = NULL;
537 break;
540 g_free(ticket_uri);
544 if (wcd) {
545 wcd->callback(sipe_private,
546 wcd->service_uri,
547 uri ? uri : NULL,
548 NULL,
549 NULL,
550 wcd->callback_data);
551 callback_data_free(wcd);
555 gboolean sipe_webticket_request(struct sipe_core_private *sipe_private,
556 struct sipe_svc_session *session,
557 const gchar *base_uri,
558 const gchar *port_name,
559 sipe_webticket_callback *callback,
560 gpointer callback_data)
562 struct webticket_callback_data *wcd = g_new0(struct webticket_callback_data, 1);
563 gboolean ret = sipe_svc_metadata(sipe_private,
564 session,
565 base_uri,
566 service_metadata,
567 wcd);
569 if (ret) {
570 wcd->service_uri = g_strdup(base_uri);
571 wcd->service_port = port_name;
572 wcd->callback = callback;
573 wcd->callback_data = callback_data;
574 wcd->session = session;
575 } else {
576 g_free(wcd);
579 return(ret);
583 Local Variables:
584 mode: c
585 c-file-style: "bsd"
586 indent-tabs-mode: t
587 tab-width: 8
588 End: