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
;
56 gboolean requires_signing
;
58 struct sipe_tls_random entropy
;
60 sipe_webticket_callback
*callback
;
61 gpointer callback_data
;
64 static void callback_data_free(struct webticket_callback_data
*wcd
)
67 sipe_tls_free_random(&wcd
->entropy
);
68 g_free(wcd
->webticket_negotiate_uri
);
69 g_free(wcd
->webticket_fedbearer_uri
);
70 g_free(wcd
->service_auth_uri
);
71 g_free(wcd
->service_uri
);
76 static gchar
*extract_raw_xml_attribute(const gchar
*xml
,
79 gchar
*attr_start
= g_strdup_printf("%s=\"", name
);
81 const gchar
*start
= strstr(xml
, attr_start
);
84 const gchar
*value
= start
+ strlen(attr_start
);
85 const gchar
*end
= strchr(value
, '"');
87 data
= g_strndup(value
, end
- value
);
95 static gchar
*extract_raw_xml(const gchar
*xml
,
99 gchar
*tag_start
= g_strdup_printf("<%s", tag
);
100 gchar
*tag_end
= g_strdup_printf("</%s>", tag
);
102 const gchar
*start
= strstr(xml
, tag_start
);
105 const gchar
*end
= strstr(start
+ strlen(tag_start
), tag_end
);
108 data
= g_strndup(start
, end
+ strlen(tag_end
) - start
);
110 const gchar
*tmp
= strchr(start
+ strlen(tag_start
), '>') + 1;
111 data
= g_strndup(tmp
, end
- tmp
);
121 static gchar
*generate_timestamp(const gchar
*raw
,
122 const gchar
*lifetime_tag
)
124 gchar
*lifetime
= extract_raw_xml(raw
, lifetime_tag
, FALSE
);
125 gchar
*timestamp
= NULL
;
127 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>",
133 static gchar
*generate_fedbearer_wsse(const gchar
*raw
)
135 gchar
*timestamp
= generate_timestamp(raw
, "wst:Lifetime");
136 gchar
*keydata
= extract_raw_xml(raw
, "EncryptedData", TRUE
);
137 gchar
*wsse_security
= NULL
;
139 if (timestamp
&& keydata
) {
140 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
141 wsse_security
= g_strconcat(timestamp
, keydata
, NULL
);
146 return(wsse_security
);
149 static gchar
*generate_sha1_proof_wsse(const gchar
*raw
,
150 struct sipe_tls_random
*entropy
)
152 gchar
*timestamp
= generate_timestamp(raw
, "Lifetime");
153 gchar
*keydata
= extract_raw_xml(raw
, "saml:Assertion", TRUE
);
154 gchar
*wsse_security
= NULL
;
156 if (timestamp
&& keydata
) {
158 gchar
*assertionID
= extract_raw_xml_attribute(keydata
,
164 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
166 * "The key is computed using P_SHA1() from the TLS sepcification to generate
167 * a bit stream using entropy from both sides. The exact form is:
169 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
171 gchar
*entropy_res_base64
= extract_raw_xml(raw
, "BinarySecret", FALSE
);
172 gsize entropy_res_length
;
173 guchar
*entropy_response
= g_base64_decode(entropy_res_base64
,
174 &entropy_res_length
);
175 guchar
*key
= sipe_tls_p_sha1(entropy
->buffer
,
180 g_free(entropy_response
);
181 g_free(entropy_res_base64
);
183 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
185 if (assertionID
&& key
) {
186 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
187 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
192 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
194 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
195 sipe_digest_sha1((guchar
*) timestamp
,
198 base64
= g_base64_encode(digest
,
199 SIPE_DIGEST_SHA1_LENGTH
);
201 /* XML-Sig: SignedInfo for reference element */
202 signed_info
= g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
203 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
204 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
205 "<Reference URI=\"#timestamp\">"
207 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
209 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
210 "<DigestValue>%s</DigestValue>"
216 /* XML-Sig: SignedInfo in canonical form */
217 canon
= sipe_xml_exc_c14n(signed_info
);
223 /* calculate signature */
224 sipe_digest_hmac_sha1(key
, entropy
->length
,
228 base64
= g_base64_encode(digest
,
229 SIPE_DIGEST_HMAC_SHA1_LENGTH
);
231 /* XML-Sig: Signature from SignedInfo + Key */
232 signature
= g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
234 " <SignatureValue>%s</SignatureValue>"
236 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
237 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
238 " </wsse:SecurityTokenReference>"
247 wsse_security
= g_strconcat(timestamp
,
259 /* token doesn't require signature */
260 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata, no signing required");
261 wsse_security
= g_strconcat(timestamp
,
269 return(wsse_security
);
272 static void webticket_token(struct sipe_core_private
*sipe_private
,
276 gpointer callback_data
)
278 struct webticket_callback_data
*wcd
= callback_data
;
279 gboolean failed
= TRUE
;
282 /* WebTicket for Web Service */
283 if (wcd
->webticket_for_service
) {
284 gchar
*wsse_security
= generate_sha1_proof_wsse(raw
,
285 wcd
->requires_signing
? &wcd
->entropy
: NULL
);
288 /* callback takes ownership of wsse_security */
289 wcd
->callback(sipe_private
,
291 wcd
->service_auth_uri
,
295 g_free(wsse_security
);
298 /* WebTicket for federated authentication */
300 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
304 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
307 if (sipe_svc_webticket(sipe_private
,
308 wcd
->webticket_fedbearer_uri
,
310 wcd
->service_auth_uri
,
314 wcd
->webticket_for_service
= TRUE
;
316 /* callback data passed down the line */
319 g_free(wsse_security
);
324 /* Retry with federated authentication? */
325 if (wcd
->webticket_fedbearer_uri
&& !wcd
->tried_fedbearer
) {
326 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
329 wcd
->tried_fedbearer
= TRUE
;
330 if (sipe_svc_webticket_lmc(sipe_private
,
331 wcd
->webticket_fedbearer_uri
,
334 wcd
->webticket_for_service
= FALSE
;
336 /* callback data passed down the line */
344 wcd
->callback(sipe_private
,
350 callback_data_free(wcd
);
354 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
356 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
358 gpointer callback_data
)
360 struct webticket_callback_data
*wcd
= callback_data
;
363 const sipe_xml
*node
;
365 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
368 /* Authentication ports accepted by WebTicket Service */
369 for (node
= sipe_xml_child(metadata
, "service/port");
371 node
= sipe_xml_twin(node
)) {
372 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
377 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
378 "WebTicketServiceWinNegotiate")) {
379 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri
);
380 g_free(wcd
->webticket_negotiate_uri
);
381 wcd
->webticket_negotiate_uri
= g_strdup(auth_uri
);
382 } else if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
384 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri
);
385 g_free(wcd
->webticket_fedbearer_uri
);
386 wcd
->webticket_fedbearer_uri
= g_strdup(auth_uri
);
391 if (wcd
->webticket_negotiate_uri
|| wcd
->webticket_fedbearer_uri
) {
394 /* Entropy: 256 random bits */
395 if (!wcd
->entropy
.buffer
)
396 sipe_tls_fill_random(&wcd
->entropy
, 256);
398 if (wcd
->webticket_negotiate_uri
) {
399 /* Try Negotiate authentication first */
401 success
= sipe_svc_webticket(sipe_private
,
402 wcd
->webticket_negotiate_uri
,
404 wcd
->service_auth_uri
,
408 wcd
->webticket_for_service
= TRUE
;
410 wcd
->tried_fedbearer
= TRUE
;
411 success
= sipe_svc_webticket_lmc(sipe_private
,
412 wcd
->webticket_fedbearer_uri
,
415 wcd
->webticket_for_service
= FALSE
;
419 /* callback data passed down the line */
426 wcd
->callback(sipe_private
,
431 callback_data_free(wcd
);
435 static void service_metadata(struct sipe_core_private
*sipe_private
,
437 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
439 gpointer callback_data
)
441 struct webticket_callback_data
*wcd
= callback_data
;
444 const sipe_xml
*node
;
445 gchar
*policy
= g_strdup_printf("%s_policy", wcd
->service_port
);
446 gchar
*ticket_uri
= NULL
;
448 SIPE_DEBUG_INFO("webservice_metadata: metadata for service %s retrieved successfully",
451 /* WebTicket policies accepted by Web Service */
452 for (node
= sipe_xml_child(metadata
, "Policy");
454 node
= sipe_xml_twin(node
)) {
455 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
458 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: WebTicket policy found");
460 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
461 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
463 /* this token type requires signing */
464 wcd
->requires_signing
= TRUE
;
466 /* try alternative token type */
467 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
468 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
471 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri
);
480 /* Authentication ports accepted by Web Service */
481 for (node
= sipe_xml_child(metadata
, "service/port");
483 node
= sipe_xml_twin(node
)) {
484 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
485 wcd
->service_port
)) {
486 const gchar
*auth_uri
;
488 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: authentication port found");
490 auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
494 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri
);
496 if (sipe_svc_metadata(sipe_private
,
500 /* Remember for later */
501 wcd
->service_auth_uri
= g_strdup(auth_uri
);
503 /* callback data passed down the line */
515 wcd
->callback(sipe_private
,
520 callback_data_free(wcd
);
524 gboolean
sipe_webticket_request(struct sipe_core_private
*sipe_private
,
525 const gchar
*base_uri
,
526 const gchar
*port_name
,
527 sipe_webticket_callback
*callback
,
528 gpointer callback_data
)
530 struct webticket_callback_data
*wcd
= g_new0(struct webticket_callback_data
, 1);
531 gboolean ret
= sipe_svc_metadata(sipe_private
,
537 wcd
->service_uri
= g_strdup(base_uri
);
538 wcd
->service_port
= port_name
;
539 wcd
->callback
= callback
;
540 wcd
->callback_data
= callback_data
;