ms-dlx: send SearchAbRequest to service
[siplcs.git] / src / core / sipe-webticket.c
blob0c3c45678a4dec944227ea591bfeb66627eae889
1 /**
2 * @file sipe-webticket.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-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;
64 static void callback_data_free(struct webticket_callback_data *wcd)
66 if (wcd) {
67 sipe_tls_free_random(&wcd->entropy);
68 g_free(wcd->webticket_negotiate_uri);
69 g_free(wcd->webticket_fedbearer_uri);
70 g_free(wcd->service_auth_uri);
71 g_free(wcd->service_uri);
72 g_free(wcd);
76 static gchar *extract_raw_xml_attribute(const gchar *xml,
77 const gchar *name)
79 gchar *attr_start = g_strdup_printf("%s=\"", name);
80 gchar *data = NULL;
81 const gchar *start = strstr(xml, attr_start);
83 if (start) {
84 const gchar *value = start + strlen(attr_start);
85 const gchar *end = strchr(value, '"');
86 if (end) {
87 data = g_strndup(value, end - value);
91 g_free(attr_start);
92 return(data);
95 static gchar *extract_raw_xml(const gchar *xml,
96 const gchar *tag,
97 gboolean include_tag)
99 gchar *tag_start = g_strdup_printf("<%s", tag);
100 gchar *tag_end = g_strdup_printf("</%s>", tag);
101 gchar *data = NULL;
102 const gchar *start = strstr(xml, tag_start);
104 if (start) {
105 const gchar *end = strstr(start + strlen(tag_start), tag_end);
106 if (end) {
107 if (include_tag) {
108 data = g_strndup(start, end + strlen(tag_end) - start);
109 } else {
110 const gchar *tmp = strchr(start + strlen(tag_start), '>') + 1;
111 data = g_strndup(tmp, end - tmp);
116 g_free(tag_end);
117 g_free(tag_start);
118 return(data);
121 static gchar *generate_timestamp(const gchar *raw,
122 const gchar *lifetime_tag)
124 gchar *lifetime = extract_raw_xml(raw, lifetime_tag, FALSE);
125 gchar *timestamp = NULL;
126 if (lifetime)
127 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>",
128 lifetime);
129 g_free(lifetime);
130 return(timestamp);
133 static gchar *generate_fedbearer_wsse(const gchar *raw)
135 gchar *timestamp = generate_timestamp(raw, "wst:Lifetime");
136 gchar *keydata = extract_raw_xml(raw, "EncryptedData", TRUE);
137 gchar *wsse_security = NULL;
139 if (timestamp && keydata) {
140 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
141 wsse_security = g_strconcat(timestamp, keydata, NULL);
144 g_free(keydata);
145 g_free(timestamp);
146 return(wsse_security);
149 static gchar *generate_sha1_proof_wsse(const gchar *raw,
150 struct sipe_tls_random *entropy)
152 gchar *timestamp = generate_timestamp(raw, "Lifetime");
153 gchar *keydata = extract_raw_xml(raw, "saml:Assertion", TRUE);
154 gchar *wsse_security = NULL;
156 if (timestamp && keydata) {
157 if (entropy) {
158 gchar *assertionID = extract_raw_xml_attribute(keydata,
159 "AssertionID");
162 * WS-Trust 1.3
164 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
166 * "The key is computed using P_SHA1() from the TLS sepcification to generate
167 * a bit stream using entropy from both sides. The exact form is:
169 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
171 gchar *entropy_res_base64 = extract_raw_xml(raw, "BinarySecret", FALSE);
172 gsize entropy_res_length;
173 guchar *entropy_response = g_base64_decode(entropy_res_base64,
174 &entropy_res_length);
175 guchar *key = sipe_tls_p_sha1(entropy->buffer,
176 entropy->length,
177 entropy_response,
178 entropy_res_length,
179 entropy->length);
180 g_free(entropy_response);
181 g_free(entropy_res_base64);
183 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
185 if (assertionID && key) {
186 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
187 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
188 gchar *base64;
189 gchar *signed_info;
190 gchar *canon;
192 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
194 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
195 sipe_digest_sha1((guchar *) timestamp,
196 strlen(timestamp),
197 digest);
198 base64 = g_base64_encode(digest,
199 SIPE_DIGEST_SHA1_LENGTH);
201 /* XML-Sig: SignedInfo for reference element */
202 signed_info = g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
203 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
204 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
205 "<Reference URI=\"#timestamp\">"
206 "<Transforms>"
207 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
208 "</Transforms>"
209 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
210 "<DigestValue>%s</DigestValue>"
211 "</Reference>"
212 "</SignedInfo>",
213 base64);
214 g_free(base64);
216 /* XML-Sig: SignedInfo in canonical form */
217 canon = sipe_xml_exc_c14n(signed_info);
218 g_free(signed_info);
220 if (canon) {
221 gchar *signature;
223 /* calculate signature */
224 sipe_digest_hmac_sha1(key, entropy->length,
225 (guchar *)canon,
226 strlen(canon),
227 digest);
228 base64 = g_base64_encode(digest,
229 SIPE_DIGEST_HMAC_SHA1_LENGTH);
231 /* XML-Sig: Signature from SignedInfo + Key */
232 signature = g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
233 " %s"
234 " <SignatureValue>%s</SignatureValue>"
235 " <KeyInfo>"
236 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
237 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
238 " </wsse:SecurityTokenReference>"
239 " </KeyInfo>"
240 "</Signature>",
241 canon,
242 base64,
243 assertionID);
244 g_free(base64);
245 g_free(canon);
247 wsse_security = g_strconcat(timestamp,
248 keydata,
249 signature,
250 NULL);
251 g_free(signature);
256 g_free(key);
257 g_free(assertionID);
258 } else {
259 /* token doesn't require signature */
260 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata, no signing required");
261 wsse_security = g_strconcat(timestamp,
262 keydata,
263 NULL);
267 g_free(keydata);
268 g_free(timestamp);
269 return(wsse_security);
272 static void webticket_token(struct sipe_core_private *sipe_private,
273 const gchar *uri,
274 const gchar *raw,
275 sipe_xml *soap_body,
276 gpointer callback_data)
278 struct webticket_callback_data *wcd = callback_data;
279 gboolean failed = TRUE;
281 if (soap_body) {
282 /* WebTicket for Web Service */
283 if (wcd->webticket_for_service) {
284 gchar *wsse_security = generate_sha1_proof_wsse(raw,
285 wcd->requires_signing ? &wcd->entropy : NULL);
287 if (wsse_security) {
288 /* callback takes ownership of wsse_security */
289 wcd->callback(sipe_private,
290 wcd->service_uri,
291 wcd->service_auth_uri,
292 wsse_security,
293 wcd->callback_data);
294 failed = FALSE;
295 g_free(wsse_security);
298 /* WebTicket for federated authentication */
299 } else {
300 gchar *wsse_security = generate_fedbearer_wsse(raw);
302 if (wsse_security) {
304 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
305 uri);
307 if (sipe_svc_webticket(sipe_private,
308 wcd->webticket_fedbearer_uri,
309 wsse_security,
310 wcd->service_auth_uri,
311 &wcd->entropy,
312 webticket_token,
313 wcd)) {
314 wcd->webticket_for_service = TRUE;
316 /* callback data passed down the line */
317 wcd = NULL;
319 g_free(wsse_security);
323 } else if (uri) {
324 /* Retry with federated authentication? */
325 if (wcd->webticket_fedbearer_uri && !wcd->tried_fedbearer) {
326 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
327 uri);
329 wcd->tried_fedbearer = TRUE;
330 if (sipe_svc_webticket_lmc(sipe_private,
331 wcd->webticket_fedbearer_uri,
332 webticket_token,
333 wcd)) {
334 wcd->webticket_for_service = FALSE;
336 /* callback data passed down the line */
337 wcd = NULL;
342 if (wcd) {
343 if (failed) {
344 wcd->callback(sipe_private,
345 wcd->service_uri,
346 uri ? uri : NULL,
347 NULL,
348 wcd->callback_data);
350 callback_data_free(wcd);
354 static void webticket_metadata(struct sipe_core_private *sipe_private,
355 const gchar *uri,
356 SIPE_UNUSED_PARAMETER const gchar *raw,
357 sipe_xml *metadata,
358 gpointer callback_data)
360 struct webticket_callback_data *wcd = callback_data;
362 if (metadata) {
363 const sipe_xml *node;
365 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
366 uri);
368 /* Authentication ports accepted by WebTicket Service */
369 for (node = sipe_xml_child(metadata, "service/port");
370 node;
371 node = sipe_xml_twin(node)) {
372 const gchar *auth_uri = sipe_xml_attribute(sipe_xml_child(node,
373 "address"),
374 "location");
376 if (auth_uri) {
377 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
378 "WebTicketServiceWinNegotiate")) {
379 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri);
380 g_free(wcd->webticket_negotiate_uri);
381 wcd->webticket_negotiate_uri = g_strdup(auth_uri);
382 } else if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
383 "WsFedBearer")) {
384 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri);
385 g_free(wcd->webticket_fedbearer_uri);
386 wcd->webticket_fedbearer_uri = g_strdup(auth_uri);
391 if (wcd->webticket_negotiate_uri || wcd->webticket_fedbearer_uri) {
392 gboolean success;
394 /* Entropy: 256 random bits */
395 if (!wcd->entropy.buffer)
396 sipe_tls_fill_random(&wcd->entropy, 256);
398 if (wcd->webticket_negotiate_uri) {
399 /* Try Negotiate authentication first */
401 success = sipe_svc_webticket(sipe_private,
402 wcd->webticket_negotiate_uri,
403 NULL,
404 wcd->service_auth_uri,
405 &wcd->entropy,
406 webticket_token,
407 wcd);
408 wcd->webticket_for_service = TRUE;
409 } else {
410 wcd->tried_fedbearer = TRUE;
411 success = sipe_svc_webticket_lmc(sipe_private,
412 wcd->webticket_fedbearer_uri,
413 webticket_token,
414 wcd);
415 wcd->webticket_for_service = FALSE;
418 if (success) {
419 /* callback data passed down the line */
420 wcd = NULL;
425 if (wcd) {
426 wcd->callback(sipe_private,
427 wcd->service_uri,
428 uri ? uri : NULL,
429 NULL,
430 wcd->callback_data);
431 callback_data_free(wcd);
435 static void service_metadata(struct sipe_core_private *sipe_private,
436 const gchar *uri,
437 SIPE_UNUSED_PARAMETER const gchar *raw,
438 sipe_xml *metadata,
439 gpointer callback_data)
441 struct webticket_callback_data *wcd = callback_data;
443 if (metadata) {
444 const sipe_xml *node;
445 gchar *policy = g_strdup_printf("%s_policy", wcd->service_port);
446 gchar *ticket_uri = NULL;
448 SIPE_DEBUG_INFO("webservice_metadata: metadata for service %s retrieved successfully",
449 uri);
451 /* WebTicket policies accepted by Web Service */
452 for (node = sipe_xml_child(metadata, "Policy");
453 node;
454 node = sipe_xml_twin(node)) {
455 if (sipe_strcase_equal(sipe_xml_attribute(node, "Id"),
456 policy)) {
458 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: WebTicket policy found");
460 ticket_uri = sipe_xml_data(sipe_xml_child(node,
461 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
462 if (ticket_uri) {
463 /* this token type requires signing */
464 wcd->requires_signing = TRUE;
465 } else {
466 /* try alternative token type */
467 ticket_uri = sipe_xml_data(sipe_xml_child(node,
468 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
470 if (ticket_uri) {
471 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri);
473 break;
476 g_free(policy);
478 if (ticket_uri) {
480 /* Authentication ports accepted by Web Service */
481 for (node = sipe_xml_child(metadata, "service/port");
482 node;
483 node = sipe_xml_twin(node)) {
484 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
485 wcd->service_port)) {
486 const gchar *auth_uri;
488 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: authentication port found");
490 auth_uri = sipe_xml_attribute(sipe_xml_child(node,
491 "address"),
492 "location");
493 if (auth_uri) {
494 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri);
496 if (sipe_svc_metadata(sipe_private,
497 ticket_uri,
498 webticket_metadata,
499 wcd)) {
500 /* Remember for later */
501 wcd->service_auth_uri = g_strdup(auth_uri);
503 /* callback data passed down the line */
504 wcd = NULL;
507 break;
510 g_free(ticket_uri);
514 if (wcd) {
515 wcd->callback(sipe_private,
516 wcd->service_uri,
517 uri ? uri : NULL,
518 NULL,
519 wcd->callback_data);
520 callback_data_free(wcd);
524 gboolean sipe_webticket_request(struct sipe_core_private *sipe_private,
525 const gchar *base_uri,
526 const gchar *port_name,
527 sipe_webticket_callback *callback,
528 gpointer callback_data)
530 struct webticket_callback_data *wcd = g_new0(struct webticket_callback_data, 1);
531 gboolean ret = sipe_svc_metadata(sipe_private,
532 base_uri,
533 service_metadata,
534 wcd);
536 if (ret) {
537 wcd->service_uri = g_strdup(base_uri);
538 wcd->service_port = port_name;
539 wcd->callback = callback;
540 wcd->callback_data = callback_data;
541 } else {
542 g_free(wcd);
545 return(ret);
549 Local Variables:
550 mode: c
551 c-file-style: "bsd"
552 indent-tabs-mode: t
553 tab-width: 8
554 End: