ms-dlx: request web ticket for service
[siplcs.git] / src / core / sipe-webticket.c
blob7094c29edcc3712f568cd58b436c4b96a3462a4f
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;
57 struct sipe_tls_random entropy;
59 sipe_webticket_callback *callback;
60 gpointer callback_data;
63 static void callback_data_free(struct webticket_callback_data *wcd)
65 if (wcd) {
66 sipe_tls_free_random(&wcd->entropy);
67 g_free(wcd->webticket_negotiate_uri);
68 g_free(wcd->webticket_fedbearer_uri);
69 g_free(wcd->service_auth_uri);
70 g_free(wcd->service_uri);
71 g_free(wcd);
75 static gchar *extract_raw_xml_attribute(const gchar *xml,
76 const gchar *name)
78 gchar *attr_start = g_strdup_printf("%s=\"", name);
79 gchar *data = NULL;
80 const gchar *start = strstr(xml, attr_start);
82 if (start) {
83 const gchar *value = start + strlen(attr_start);
84 const gchar *end = strchr(value, '"');
85 if (end) {
86 data = g_strndup(value, end - value);
90 g_free(attr_start);
91 return(data);
94 static gchar *extract_raw_xml(const gchar *xml,
95 const gchar *tag,
96 gboolean include_tag)
98 gchar *tag_start = g_strdup_printf("<%s", tag);
99 gchar *tag_end = g_strdup_printf("</%s>", tag);
100 gchar *data = NULL;
101 const gchar *start = strstr(xml, tag_start);
103 if (start) {
104 const gchar *end = strstr(start + strlen(tag_start), tag_end);
105 if (end) {
106 if (include_tag) {
107 data = g_strndup(start, end + strlen(tag_end) - start);
108 } else {
109 const gchar *tmp = strchr(start + strlen(tag_start), '>') + 1;
110 data = g_strndup(tmp, end - tmp);
115 g_free(tag_end);
116 g_free(tag_start);
117 return(data);
120 static gchar *generate_timestamp(const gchar *raw,
121 const gchar *lifetime_tag)
123 gchar *lifetime = extract_raw_xml(raw, lifetime_tag, FALSE);
124 gchar *timestamp = NULL;
125 if (lifetime)
126 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>",
127 lifetime);
128 g_free(lifetime);
129 return(timestamp);
132 static gchar *generate_fedbearer_wsse(const gchar *raw)
134 gchar *timestamp = generate_timestamp(raw, "wst:Lifetime");
135 gchar *keydata = extract_raw_xml(raw, "EncryptedData", TRUE);
136 gchar *wsse_security = NULL;
138 if (timestamp && keydata) {
139 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
140 wsse_security = g_strconcat(timestamp, keydata, NULL);
143 g_free(keydata);
144 g_free(timestamp);
145 return(wsse_security);
148 static gchar *generate_sha1_proof_wsse(const gchar *raw,
149 struct sipe_tls_random *entropy)
151 gchar *timestamp = generate_timestamp(raw, "Lifetime");
152 gchar *keydata = extract_raw_xml(raw, "saml:Assertion", TRUE);
153 gchar *wsse_security = NULL;
155 if (timestamp && keydata) {
156 gchar *assertionID = extract_raw_xml_attribute(keydata,
157 "AssertionID");
160 * WS-Trust 1.3
162 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
164 * "The key is computed using P_SHA1() from the TLS sepcification to generate
165 * a bit stream using entropy from both sides. The exact form is:
167 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
169 gchar *entropy_res_base64 = extract_raw_xml(raw, "BinarySecret", FALSE);
170 gsize entropy_res_length;
171 guchar *entropy_response = g_base64_decode(entropy_res_base64,
172 &entropy_res_length);
173 guchar *key = sipe_tls_p_sha1(entropy->buffer,
174 entropy->length,
175 entropy_response,
176 entropy_res_length,
177 entropy->length);
178 g_free(entropy_response);
179 g_free(entropy_res_base64);
181 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
183 if (assertionID && key) {
184 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
185 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
186 gchar *base64;
187 gchar *signed_info;
188 gchar *canon;
190 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
192 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
193 sipe_digest_sha1((guchar *) timestamp,
194 strlen(timestamp),
195 digest);
196 base64 = g_base64_encode(digest,
197 SIPE_DIGEST_SHA1_LENGTH);
199 /* XML-Sig: SignedInfo for reference element */
200 signed_info = g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
201 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
202 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
203 "<Reference URI=\"#timestamp\">"
204 "<Transforms>"
205 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
206 "</Transforms>"
207 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
208 "<DigestValue>%s</DigestValue>"
209 "</Reference>"
210 "</SignedInfo>",
211 base64);
212 g_free(base64);
214 /* XML-Sig: SignedInfo in canonical form */
215 canon = sipe_xml_exc_c14n(signed_info);
216 g_free(signed_info);
218 if (canon) {
219 gchar *signature;
221 /* calculate signature */
222 sipe_digest_hmac_sha1(key, entropy->length,
223 (guchar *)canon,
224 strlen(canon),
225 digest);
226 base64 = g_base64_encode(digest,
227 SIPE_DIGEST_HMAC_SHA1_LENGTH);
229 /* XML-Sig: Signature from SignedInfo + Key */
230 signature = g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
231 " %s"
232 " <SignatureValue>%s</SignatureValue>"
233 " <KeyInfo>"
234 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
235 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
236 " </wsse:SecurityTokenReference>"
237 " </KeyInfo>"
238 "</Signature>",
239 canon,
240 base64,
241 assertionID);
242 g_free(base64);
243 g_free(canon);
245 wsse_security = g_strconcat(timestamp,
246 keydata,
247 signature,
248 NULL);
249 g_free(signature);
254 g_free(key);
255 g_free(assertionID);
258 g_free(keydata);
259 g_free(timestamp);
260 return(wsse_security);
263 static void webticket_token(struct sipe_core_private *sipe_private,
264 const gchar *uri,
265 const gchar *raw,
266 sipe_xml *soap_body,
267 gpointer callback_data)
269 struct webticket_callback_data *wcd = callback_data;
270 gboolean failed = TRUE;
272 if (soap_body) {
273 /* WebTicket for Web Service */
274 if (wcd->webticket_for_service) {
275 gchar *wsse_security = generate_sha1_proof_wsse(raw,
276 &wcd->entropy);
278 if (wsse_security) {
279 /* callback takes ownership of wsse_security */
280 wcd->callback(sipe_private,
281 wcd->service_uri,
282 wcd->service_auth_uri,
283 wsse_security,
284 wcd->callback_data);
285 failed = FALSE;
286 g_free(wsse_security);
289 /* WebTicket for federated authentication */
290 } else {
291 gchar *wsse_security = generate_fedbearer_wsse(raw);
293 if (wsse_security) {
295 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
296 uri);
298 if (sipe_svc_webticket(sipe_private,
299 wcd->webticket_fedbearer_uri,
300 wsse_security,
301 wcd->service_auth_uri,
302 &wcd->entropy,
303 webticket_token,
304 wcd)) {
305 wcd->webticket_for_service = TRUE;
307 /* callback data passed down the line */
308 wcd = NULL;
310 g_free(wsse_security);
314 } else if (uri) {
315 /* Retry with federated authentication? */
316 if (wcd->webticket_fedbearer_uri && !wcd->tried_fedbearer) {
317 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
318 uri);
320 wcd->tried_fedbearer = TRUE;
321 if (sipe_svc_webticket_lmc(sipe_private,
322 wcd->webticket_fedbearer_uri,
323 webticket_token,
324 wcd)) {
325 wcd->webticket_for_service = FALSE;
327 /* callback data passed down the line */
328 wcd = NULL;
333 if (wcd) {
334 if (failed) {
335 wcd->callback(sipe_private,
336 wcd->service_uri,
337 uri ? uri : NULL,
338 NULL,
339 wcd->callback_data);
341 callback_data_free(wcd);
345 static void webticket_metadata(struct sipe_core_private *sipe_private,
346 const gchar *uri,
347 SIPE_UNUSED_PARAMETER const gchar *raw,
348 sipe_xml *metadata,
349 gpointer callback_data)
351 struct webticket_callback_data *wcd = callback_data;
353 if (metadata) {
354 const sipe_xml *node;
356 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
357 uri);
359 /* Authentication ports accepted by WebTicket Service */
360 for (node = sipe_xml_child(metadata, "service/port");
361 node;
362 node = sipe_xml_twin(node)) {
363 const gchar *auth_uri = sipe_xml_attribute(sipe_xml_child(node,
364 "address"),
365 "location");
367 if (auth_uri) {
368 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
369 "WebTicketServiceWinNegotiate")) {
370 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri);
371 g_free(wcd->webticket_negotiate_uri);
372 wcd->webticket_negotiate_uri = g_strdup(auth_uri);
373 } else if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
374 "WsFedBearer")) {
375 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri);
376 g_free(wcd->webticket_fedbearer_uri);
377 wcd->webticket_fedbearer_uri = g_strdup(auth_uri);
382 if (wcd->webticket_negotiate_uri || wcd->webticket_fedbearer_uri) {
383 gboolean success;
385 /* Entropy: 256 random bits */
386 if (!wcd->entropy.buffer)
387 sipe_tls_fill_random(&wcd->entropy, 256);
389 if (wcd->webticket_negotiate_uri) {
390 /* Try Negotiate authentication first */
392 success = sipe_svc_webticket(sipe_private,
393 wcd->webticket_negotiate_uri,
394 NULL,
395 wcd->service_auth_uri,
396 &wcd->entropy,
397 webticket_token,
398 wcd);
399 wcd->webticket_for_service = TRUE;
400 } else {
401 wcd->tried_fedbearer = TRUE;
402 success = sipe_svc_webticket_lmc(sipe_private,
403 wcd->webticket_fedbearer_uri,
404 webticket_token,
405 wcd);
406 wcd->webticket_for_service = FALSE;
409 if (success) {
410 /* callback data passed down the line */
411 wcd = NULL;
416 if (wcd) {
417 wcd->callback(sipe_private,
418 wcd->service_uri,
419 uri ? uri : NULL,
420 NULL,
421 wcd->callback_data);
422 callback_data_free(wcd);
426 static void service_metadata(struct sipe_core_private *sipe_private,
427 const gchar *uri,
428 SIPE_UNUSED_PARAMETER const gchar *raw,
429 sipe_xml *metadata,
430 gpointer callback_data)
432 struct webticket_callback_data *wcd = callback_data;
434 if (metadata) {
435 const sipe_xml *node;
436 gchar *policy = g_strdup_printf("%s_policy", wcd->service_port);
437 gchar *ticket_uri = NULL;
439 SIPE_DEBUG_INFO("webservice_metadata: metadata for service %s retrieved successfully",
440 uri);
442 /* WebTicket policies accepted by Web Service */
443 for (node = sipe_xml_child(metadata, "Policy");
444 node;
445 node = sipe_xml_twin(node)) {
446 if (sipe_strcase_equal(sipe_xml_attribute(node, "Id"),
447 policy)) {
449 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: WebTicket policy found");
451 ticket_uri = sipe_xml_data(sipe_xml_child(node,
452 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
453 if (!ticket_uri)
454 ticket_uri = sipe_xml_data(sipe_xml_child(node,
455 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
456 if (ticket_uri) {
457 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri);
459 break;
462 g_free(policy);
464 if (ticket_uri) {
466 /* Authentication ports accepted by Web Service */
467 for (node = sipe_xml_child(metadata, "service/port");
468 node;
469 node = sipe_xml_twin(node)) {
470 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
471 wcd->service_port)) {
472 const gchar *auth_uri;
474 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: authentication port found");
476 auth_uri = sipe_xml_attribute(sipe_xml_child(node,
477 "address"),
478 "location");
479 if (auth_uri) {
480 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri);
482 if (sipe_svc_metadata(sipe_private,
483 ticket_uri,
484 webticket_metadata,
485 wcd)) {
486 /* Remember for later */
487 wcd->service_auth_uri = g_strdup(auth_uri);
489 /* callback data passed down the line */
490 wcd = NULL;
493 break;
496 g_free(ticket_uri);
500 if (wcd) {
501 wcd->callback(sipe_private,
502 wcd->service_uri,
503 uri ? uri : NULL,
504 NULL,
505 wcd->callback_data);
506 callback_data_free(wcd);
510 gboolean sipe_webticket_request(struct sipe_core_private *sipe_private,
511 const gchar *base_uri,
512 const gchar *port_name,
513 sipe_webticket_callback *callback,
514 gpointer callback_data)
516 struct webticket_callback_data *wcd = g_new0(struct webticket_callback_data, 1);
517 gboolean ret = sipe_svc_metadata(sipe_private,
518 base_uri,
519 service_metadata,
520 wcd);
522 if (ret) {
523 wcd->service_uri = g_strdup(base_uri);
524 wcd->service_port = port_name;
525 wcd->callback = callback;
526 wcd->callback_data = callback_data;
527 } else {
528 g_free(wcd);
531 return(ret);
535 Local Variables:
536 mode: c
537 c-file-style: "bsd"
538 indent-tabs-mode: t
539 tab-width: 8
540 End: