webticket: add failure message to callback
[siplcs.git] / src / core / sipe-webticket.c
blob5d544ccfd8bc90caa5f2c119bf11b07af6ae0ece
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 void webticket_token(struct sipe_core_private *sipe_private,
249 const gchar *uri,
250 const gchar *raw,
251 sipe_xml *soap_body,
252 gpointer callback_data)
254 struct webticket_callback_data *wcd = callback_data;
255 gboolean failed = TRUE;
257 if (soap_body) {
258 /* WebTicket for Web Service */
259 if (wcd->webticket_for_service) {
260 gchar *wsse_security = generate_sha1_proof_wsse(raw,
261 wcd->requires_signing ? &wcd->entropy : NULL);
263 if (wsse_security) {
264 /* callback takes ownership of wsse_security */
265 wcd->callback(sipe_private,
266 wcd->service_uri,
267 wcd->service_auth_uri,
268 wsse_security,
269 NULL,
270 wcd->callback_data);
271 failed = FALSE;
272 g_free(wsse_security);
275 /* WebTicket for federated authentication */
276 } else {
277 gchar *wsse_security = generate_fedbearer_wsse(raw);
279 if (wsse_security) {
281 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
282 uri);
284 if (sipe_svc_webticket(sipe_private,
285 wcd->session,
286 wcd->webticket_fedbearer_uri,
287 wsse_security,
288 wcd->service_auth_uri,
289 &wcd->entropy,
290 webticket_token,
291 wcd)) {
292 wcd->webticket_for_service = TRUE;
294 /* callback data passed down the line */
295 wcd = NULL;
297 g_free(wsse_security);
301 } else if (uri) {
302 /* Retry with federated authentication? */
303 if (wcd->webticket_fedbearer_uri && !wcd->tried_fedbearer) {
304 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
305 uri);
307 wcd->tried_fedbearer = TRUE;
308 if (sipe_svc_webticket_lmc(sipe_private,
309 wcd->session,
310 wcd->webticket_fedbearer_uri,
311 webticket_token,
312 wcd)) {
313 wcd->webticket_for_service = FALSE;
315 /* callback data passed down the line */
316 wcd = NULL;
321 if (wcd) {
322 if (failed) {
323 wcd->callback(sipe_private,
324 wcd->service_uri,
325 uri ? uri : NULL,
326 NULL,
327 NULL,
328 wcd->callback_data);
330 callback_data_free(wcd);
334 static void webticket_metadata(struct sipe_core_private *sipe_private,
335 const gchar *uri,
336 SIPE_UNUSED_PARAMETER const gchar *raw,
337 sipe_xml *metadata,
338 gpointer callback_data)
340 struct webticket_callback_data *wcd = callback_data;
342 if (metadata) {
343 const sipe_xml *node;
345 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
346 uri);
348 /* Authentication ports accepted by WebTicket Service */
349 for (node = sipe_xml_child(metadata, "service/port");
350 node;
351 node = sipe_xml_twin(node)) {
352 const gchar *auth_uri = sipe_xml_attribute(sipe_xml_child(node,
353 "address"),
354 "location");
356 if (auth_uri) {
357 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
358 "WebTicketServiceWinNegotiate")) {
359 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri);
360 g_free(wcd->webticket_negotiate_uri);
361 wcd->webticket_negotiate_uri = g_strdup(auth_uri);
362 } else if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
363 "WsFedBearer")) {
364 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri);
365 g_free(wcd->webticket_fedbearer_uri);
366 wcd->webticket_fedbearer_uri = g_strdup(auth_uri);
371 if (wcd->webticket_negotiate_uri || wcd->webticket_fedbearer_uri) {
372 gboolean success;
374 /* Entropy: 256 random bits */
375 if (!wcd->entropy.buffer)
376 sipe_tls_fill_random(&wcd->entropy, 256);
378 if (wcd->webticket_negotiate_uri) {
379 /* Try Negotiate authentication first */
381 success = sipe_svc_webticket(sipe_private,
382 wcd->session,
383 wcd->webticket_negotiate_uri,
384 NULL,
385 wcd->service_auth_uri,
386 &wcd->entropy,
387 webticket_token,
388 wcd);
389 wcd->webticket_for_service = TRUE;
390 } else {
391 wcd->tried_fedbearer = TRUE;
392 success = sipe_svc_webticket_lmc(sipe_private,
393 wcd->session,
394 wcd->webticket_fedbearer_uri,
395 webticket_token,
396 wcd);
397 wcd->webticket_for_service = FALSE;
400 if (success) {
401 /* callback data passed down the line */
402 wcd = NULL;
407 if (wcd) {
408 wcd->callback(sipe_private,
409 wcd->service_uri,
410 uri ? uri : NULL,
411 NULL,
412 NULL,
413 wcd->callback_data);
414 callback_data_free(wcd);
418 static void service_metadata(struct sipe_core_private *sipe_private,
419 const gchar *uri,
420 SIPE_UNUSED_PARAMETER const gchar *raw,
421 sipe_xml *metadata,
422 gpointer callback_data)
424 struct webticket_callback_data *wcd = callback_data;
426 if (metadata) {
427 const sipe_xml *node;
428 gchar *policy = g_strdup_printf("%s_policy", wcd->service_port);
429 gchar *ticket_uri = NULL;
431 SIPE_DEBUG_INFO("webservice_metadata: metadata for service %s retrieved successfully",
432 uri);
434 /* WebTicket policies accepted by Web Service */
435 for (node = sipe_xml_child(metadata, "Policy");
436 node;
437 node = sipe_xml_twin(node)) {
438 if (sipe_strcase_equal(sipe_xml_attribute(node, "Id"),
439 policy)) {
441 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: WebTicket policy found");
443 ticket_uri = sipe_xml_data(sipe_xml_child(node,
444 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
445 if (ticket_uri) {
446 /* this token type requires signing */
447 wcd->requires_signing = TRUE;
448 } else {
449 /* try alternative token type */
450 ticket_uri = sipe_xml_data(sipe_xml_child(node,
451 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
453 if (ticket_uri) {
454 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri);
456 break;
459 g_free(policy);
461 if (ticket_uri) {
463 /* Authentication ports accepted by Web Service */
464 for (node = sipe_xml_child(metadata, "service/port");
465 node;
466 node = sipe_xml_twin(node)) {
467 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
468 wcd->service_port)) {
469 const gchar *auth_uri;
471 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: authentication port found");
473 auth_uri = sipe_xml_attribute(sipe_xml_child(node,
474 "address"),
475 "location");
476 if (auth_uri) {
477 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri);
479 if (sipe_svc_metadata(sipe_private,
480 wcd->session,
481 ticket_uri,
482 webticket_metadata,
483 wcd)) {
484 /* Remember for later */
485 wcd->service_auth_uri = g_strdup(auth_uri);
487 /* callback data passed down the line */
488 wcd = NULL;
491 break;
494 g_free(ticket_uri);
498 if (wcd) {
499 wcd->callback(sipe_private,
500 wcd->service_uri,
501 uri ? uri : NULL,
502 NULL,
503 NULL,
504 wcd->callback_data);
505 callback_data_free(wcd);
509 gboolean sipe_webticket_request(struct sipe_core_private *sipe_private,
510 struct sipe_svc_session *session,
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 session,
519 base_uri,
520 service_metadata,
521 wcd);
523 if (ret) {
524 wcd->service_uri = g_strdup(base_uri);
525 wcd->service_port = port_name;
526 wcd->callback = callback;
527 wcd->callback_data = callback_data;
528 wcd->session = session;
529 } else {
530 g_free(wcd);
533 return(ret);
537 Local Variables:
538 mode: c
539 c-file-style: "bsd"
540 indent-tabs-mode: t
541 tab-width: 8
542 End: