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 void webticket_token(struct sipe_core_private
*sipe_private
,
252 gpointer callback_data
)
254 struct webticket_callback_data
*wcd
= callback_data
;
255 gboolean failed
= TRUE
;
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
);
264 /* callback takes ownership of wsse_security */
265 wcd
->callback(sipe_private
,
267 wcd
->service_auth_uri
,
272 g_free(wsse_security
);
275 /* WebTicket for federated authentication */
277 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
281 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
284 if (sipe_svc_webticket(sipe_private
,
286 wcd
->webticket_fedbearer_uri
,
288 wcd
->service_auth_uri
,
292 wcd
->webticket_for_service
= TRUE
;
294 /* callback data passed down the line */
297 g_free(wsse_security
);
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",
307 wcd
->tried_fedbearer
= TRUE
;
308 if (sipe_svc_webticket_lmc(sipe_private
,
310 wcd
->webticket_fedbearer_uri
,
313 wcd
->webticket_for_service
= FALSE
;
315 /* callback data passed down the line */
323 wcd
->callback(sipe_private
,
330 callback_data_free(wcd
);
334 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
336 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
338 gpointer callback_data
)
340 struct webticket_callback_data
*wcd
= callback_data
;
343 const sipe_xml
*node
;
345 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
348 /* Authentication ports accepted by WebTicket Service */
349 for (node
= sipe_xml_child(metadata
, "service/port");
351 node
= sipe_xml_twin(node
)) {
352 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
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"),
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
) {
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
,
383 wcd
->webticket_negotiate_uri
,
385 wcd
->service_auth_uri
,
389 wcd
->webticket_for_service
= TRUE
;
391 wcd
->tried_fedbearer
= TRUE
;
392 success
= sipe_svc_webticket_lmc(sipe_private
,
394 wcd
->webticket_fedbearer_uri
,
397 wcd
->webticket_for_service
= FALSE
;
401 /* callback data passed down the line */
408 wcd
->callback(sipe_private
,
414 callback_data_free(wcd
);
418 static void service_metadata(struct sipe_core_private
*sipe_private
,
420 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
422 gpointer callback_data
)
424 struct webticket_callback_data
*wcd
= callback_data
;
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",
434 /* WebTicket policies accepted by Web Service */
435 for (node
= sipe_xml_child(metadata
, "Policy");
437 node
= sipe_xml_twin(node
)) {
438 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
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"));
446 /* this token type requires signing */
447 wcd
->requires_signing
= TRUE
;
449 /* try alternative token type */
450 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
451 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
454 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri
);
463 /* Authentication ports accepted by Web Service */
464 for (node
= sipe_xml_child(metadata
, "service/port");
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
,
477 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri
);
479 if (sipe_svc_metadata(sipe_private
,
484 /* Remember for later */
485 wcd
->service_auth_uri
= g_strdup(auth_uri
);
487 /* callback data passed down the line */
499 wcd
->callback(sipe_private
,
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
,
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
;