2 * @file sipe-webticket.c
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
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"
42 #include "sipe-webticket.h"
43 #include "sipe-utils.h"
46 struct webticket_callback_data
{
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
)
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
);
78 static gchar
*extract_raw_xml_attribute(const gchar
*xml
,
81 gchar
*attr_start
= g_strdup_printf("%s=\"", name
);
83 const gchar
*start
= strstr(xml
, attr_start
);
86 const gchar
*value
= start
+ strlen(attr_start
);
87 const gchar
*end
= strchr(value
, '"');
89 data
= g_strndup(value
, end
- value
);
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
;
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>",
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
);
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
) {
134 gchar
*assertionID
= extract_raw_xml_attribute(keydata
,
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
,
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
];
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
,
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\">"
183 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
185 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
186 "<DigestValue>%s</DigestValue>"
192 /* XML-Sig: SignedInfo in canonical form */
193 canon
= sipe_xml_exc_c14n(signed_info
);
199 /* calculate signature */
200 sipe_digest_hmac_sha1(key
, entropy
->length
,
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#\">"
210 " <SignatureValue>%s</SignatureValue>"
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>"
223 wsse_security
= g_strconcat(timestamp
,
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
,
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
,
254 gpointer callback_data
)
256 struct webticket_callback_data
*wcd
= callback_data
;
257 gboolean failed
= TRUE
;
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
);
266 /* callback takes ownership of wsse_security */
267 wcd
->callback(sipe_private
,
269 wcd
->service_auth_uri
,
274 g_free(wsse_security
);
277 /* WebTicket for federated authentication */
279 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
283 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
286 if (sipe_svc_webticket(sipe_private
,
288 wcd
->webticket_fedbearer_uri
,
290 wcd
->service_auth_uri
,
294 wcd
->webticket_for_service
= TRUE
;
296 /* callback data passed down the line */
299 g_free(wsse_security
);
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",
309 if (initiate_fedbearer(sipe_private
, wcd
)) {
310 /* callback data passed down the line */
318 gchar
*failure_msg
= NULL
;
321 failure_msg
= sipe_xml_data(sipe_xml_child(soap_body
,
322 "Body/Fault/Detail/error/internalerror/text"));
323 /* XML data can end in 
 */
324 g_strstrip(failure_msg
);
327 wcd
->callback(sipe_private
,
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
,
343 gpointer callback_data
)
345 struct webticket_callback_data
*wcd
= callback_data
;
348 SIPE_DEBUG_INFO("realminfo: data for user %s retrieved successfully",
349 sipe_private
->username
);
351 if (sipe_svc_webticket_lmc(sipe_private
,
353 wcd
->webticket_fedbearer_uri
,
356 /* callback data passed down the line */
362 wcd
->callback(sipe_private
,
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
,
379 wcd
->tried_fedbearer
= TRUE
;
380 wcd
->webticket_for_service
= FALSE
;
385 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
387 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
389 gpointer callback_data
)
391 struct webticket_callback_data
*wcd
= callback_data
;
394 const sipe_xml
*node
;
396 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
399 /* Authentication ports accepted by WebTicket Service */
400 for (node
= sipe_xml_child(metadata
, "service/port");
402 node
= sipe_xml_twin(node
)) {
403 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
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"),
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
) {
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
,
434 wcd
->webticket_negotiate_uri
,
436 wcd
->service_auth_uri
,
440 wcd
->webticket_for_service
= TRUE
;
442 success
= initiate_fedbearer(sipe_private
,
447 /* callback data passed down the line */
454 wcd
->callback(sipe_private
,
460 callback_data_free(wcd
);
464 static void service_metadata(struct sipe_core_private
*sipe_private
,
466 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
468 gpointer callback_data
)
470 struct webticket_callback_data
*wcd
= callback_data
;
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",
480 /* WebTicket policies accepted by Web Service */
481 for (node
= sipe_xml_child(metadata
, "Policy");
483 node
= sipe_xml_twin(node
)) {
484 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
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"));
492 /* this token type requires signing */
493 wcd
->requires_signing
= TRUE
;
495 /* try alternative token type */
496 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
497 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
500 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri
);
509 /* Authentication ports accepted by Web Service */
510 for (node
= sipe_xml_child(metadata
, "service/port");
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
,
523 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri
);
525 if (sipe_svc_metadata(sipe_private
,
530 /* Remember for later */
531 wcd
->service_auth_uri
= g_strdup(auth_uri
);
533 /* callback data passed down the line */
545 wcd
->callback(sipe_private
,
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
,
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
;