2 * @file sipe-webticket.c
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
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
;
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
)
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
);
75 static gchar
*extract_raw_xml_attribute(const gchar
*xml
,
78 gchar
*attr_start
= g_strdup_printf("%s=\"", name
);
80 const gchar
*start
= strstr(xml
, attr_start
);
83 const gchar
*value
= start
+ strlen(attr_start
);
84 const gchar
*end
= strchr(value
, '"');
86 data
= g_strndup(value
, end
- value
);
94 static gchar
*extract_raw_xml(const gchar
*xml
,
98 gchar
*tag_start
= g_strdup_printf("<%s", tag
);
99 gchar
*tag_end
= g_strdup_printf("</%s>", tag
);
101 const gchar
*start
= strstr(xml
, tag_start
);
104 const gchar
*end
= strstr(start
+ strlen(tag_start
), tag_end
);
107 data
= g_strndup(start
, end
+ strlen(tag_end
) - start
);
109 const gchar
*tmp
= strchr(start
+ strlen(tag_start
), '>') + 1;
110 data
= g_strndup(tmp
, end
- tmp
);
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
;
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>",
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
);
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
,
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
,
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
];
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
,
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\">"
205 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
207 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
208 "<DigestValue>%s</DigestValue>"
214 /* XML-Sig: SignedInfo in canonical form */
215 canon
= sipe_xml_exc_c14n(signed_info
);
221 /* calculate signature */
222 sipe_digest_hmac_sha1(key
, entropy
->length
,
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#\">"
232 " <SignatureValue>%s</SignatureValue>"
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>"
245 wsse_security
= g_strconcat(timestamp
,
260 return(wsse_security
);
263 static void webticket_token(struct sipe_core_private
*sipe_private
,
267 gpointer callback_data
)
269 struct webticket_callback_data
*wcd
= callback_data
;
270 gboolean failed
= TRUE
;
273 /* WebTicket for Web Service */
274 if (wcd
->webticket_for_service
) {
275 gchar
*wsse_security
= generate_sha1_proof_wsse(raw
,
279 /* callback takes ownership of wsse_security */
280 wcd
->callback(sipe_private
,
282 wcd
->service_auth_uri
,
286 g_free(wsse_security
);
289 /* WebTicket for federated authentication */
291 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
295 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
298 if (sipe_svc_webticket(sipe_private
,
299 wcd
->webticket_fedbearer_uri
,
301 wcd
->service_auth_uri
,
305 wcd
->webticket_for_service
= TRUE
;
307 /* callback data passed down the line */
310 g_free(wsse_security
);
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",
320 wcd
->tried_fedbearer
= TRUE
;
321 if (sipe_svc_webticket_lmc(sipe_private
,
322 wcd
->webticket_fedbearer_uri
,
325 wcd
->webticket_for_service
= FALSE
;
327 /* callback data passed down the line */
335 wcd
->callback(sipe_private
,
341 callback_data_free(wcd
);
345 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
347 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
349 gpointer callback_data
)
351 struct webticket_callback_data
*wcd
= callback_data
;
354 const sipe_xml
*node
;
356 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
359 /* Authentication ports accepted by WebTicket Service */
360 for (node
= sipe_xml_child(metadata
, "service/port");
362 node
= sipe_xml_twin(node
)) {
363 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
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"),
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
) {
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
,
395 wcd
->service_auth_uri
,
399 wcd
->webticket_for_service
= TRUE
;
401 wcd
->tried_fedbearer
= TRUE
;
402 success
= sipe_svc_webticket_lmc(sipe_private
,
403 wcd
->webticket_fedbearer_uri
,
406 wcd
->webticket_for_service
= FALSE
;
410 /* callback data passed down the line */
417 wcd
->callback(sipe_private
,
422 callback_data_free(wcd
);
426 static void service_metadata(struct sipe_core_private
*sipe_private
,
428 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
430 gpointer callback_data
)
432 struct webticket_callback_data
*wcd
= callback_data
;
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",
442 /* WebTicket policies accepted by Web Service */
443 for (node
= sipe_xml_child(metadata
, "Policy");
445 node
= sipe_xml_twin(node
)) {
446 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
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"));
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
,
483 /* Remember for later */
484 wcd
->service_auth_uri
= g_strdup(auth_uri
);
486 /* callback data passed down the line */
498 wcd
->callback(sipe_private
,
503 callback_data_free(wcd
);
507 gboolean
sipe_webticket_request(struct sipe_core_private
*sipe_private
,
508 const gchar
*base_uri
,
509 const gchar
*port_name
,
510 sipe_webticket_callback
*callback
,
511 gpointer callback_data
)
513 struct webticket_callback_data
*wcd
= g_new0(struct webticket_callback_data
, 1);
514 gboolean ret
= sipe_svc_metadata(sipe_private
,
520 wcd
->service_uri
= g_strdup(base_uri
);
521 wcd
->service_port
= port_name
;
522 wcd
->callback
= callback
;
523 wcd
->callback_data
= callback_data
;