xml: add function for "Exclusive XML Canonicalization"
[siplcs.git] / src / core / sipe-certificate.c
blob0c1c33bde2ccf26557b462b13da1e1bee10c0afc
1 /**
2 * @file sipe-certificate.c
4 * pidgin-sipe
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-SIPAE]: http://msdn.microsoft.com/en-us/library/cc431510.aspx
27 * - [MS-OCAUTHWS]: http://msdn.microsoft.com/en-us/library/ff595592.aspx
28 * - MS Tech-Ed Europe 2010 "UNC310: Microsoft Lync 2010 Technology Explained"
29 * http://ecn.channel9.msdn.com/o9/te/Europe/2010/pptx/unc310.pptx
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
36 #include <string.h>
38 #include <glib.h>
40 #include "sipe-common.h"
41 #include "sipe-backend.h"
42 #include "sipe-core.h"
43 #include "sipe-core-private.h"
44 #include "sipe-certificate.h"
45 #include "sipe-digest.h"
46 #include "sipe-nls.h"
47 #include "sipe-svc.h"
48 #include "sipe-utils.h"
49 #include "sipe-xml.h"
51 struct certificate_callback_data {
52 gchar *target;
53 gchar *authuser;
54 gchar *webticket_anon_uri;
55 gchar *webticket_fedbearer_uri;
56 gchar *certprov_uri;
58 gboolean tried_fedbearer;
59 gboolean webticket_for_certprov;
61 struct sipe_svc_random entropy;
64 static void callback_data_free(struct certificate_callback_data *ccd)
66 if (ccd) {
67 g_free(ccd->target);
68 g_free(ccd->authuser);
69 g_free(ccd->webticket_anon_uri);
70 g_free(ccd->webticket_fedbearer_uri);
71 g_free(ccd->certprov_uri);
72 sipe_svc_free_random(&ccd->entropy);
73 g_free(ccd);
77 gpointer sipe_certificate_tls_dsk_find(struct sipe_core_private *sipe_private,
78 const gchar *target)
80 if (!target)
81 return(NULL);
83 /* temporary */
84 (void)sipe_private;
86 return(NULL);
89 static void certificate_failure(struct sipe_core_private *sipe_private,
90 const gchar *format,
91 const gchar *parameter)
93 gchar *tmp = g_strdup_printf(format, parameter);
94 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
95 SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
96 tmp);
97 g_free(tmp);
100 static void get_and_publish_cert(struct sipe_core_private *sipe_private,
101 const gchar *uri,
102 const gchar *raw,
103 sipe_xml *soap_body,
104 gpointer callback_data)
106 struct certificate_callback_data *ccd = callback_data;
108 if (soap_body) {
110 /* TBD.... */
111 (void)raw;
113 } else if (uri) {
114 certificate_failure(sipe_private,
115 _("Certitifcate request to %s failed"),
116 uri);
119 callback_data_free(ccd);
122 static gchar *extract_raw_xml_attribute(const gchar *xml,
123 const gchar *name)
125 gchar *attr_start = g_strdup_printf("%s=\"", name);
126 gchar *data = NULL;
127 const gchar *start = strstr(xml, attr_start);
129 if (start) {
130 const gchar *value = start + strlen(attr_start);
131 const gchar *end = strchr(value, '"');
132 if (end) {
133 data = g_strndup(value, end - value);
137 g_free(attr_start);
138 return(data);
141 static gchar *extract_raw_xml(const gchar *xml,
142 const gchar *tag,
143 gboolean include_tag)
145 gchar *tag_start = g_strdup_printf("<%s", tag);
146 gchar *tag_end = g_strdup_printf("</%s>", tag);
147 gchar *data = NULL;
148 const gchar *start = strstr(xml, tag_start);
150 if (start) {
151 const gchar *end = strstr(start + strlen(tag_start), tag_end);
152 if (end) {
153 if (include_tag) {
154 data = g_strndup(start, end + strlen(tag_end) - start);
155 } else {
156 const gchar *tmp = strchr(start + strlen(tag_start), '>') + 1;
157 data = g_strndup(tmp, end - tmp);
162 g_free(tag_end);
163 g_free(tag_start);
164 return(data);
167 static gchar *generate_timestamp(const gchar *raw,
168 const gchar *lifetime_tag)
170 gchar *lifetime = extract_raw_xml(raw, lifetime_tag, FALSE);
171 gchar *timestamp = NULL;
172 if (lifetime)
173 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>",
174 lifetime);
175 g_free(lifetime);
176 return(timestamp);
179 static gchar *generate_fedbearer_wsse(const gchar *raw)
181 gchar *timestamp = generate_timestamp(raw, "wst:Lifetime");
182 gchar *keydata = extract_raw_xml(raw, "EncryptedData", TRUE);
183 gchar *wsse_security = NULL;
185 if (timestamp && keydata) {
186 wsse_security = g_strconcat(timestamp, keydata, NULL);
189 g_free(keydata);
190 g_free(timestamp);
191 return(wsse_security);
194 static gchar *generate_sha1_proof_wsse(const gchar *raw,
195 struct sipe_svc_random *entropy)
197 gchar *timestamp = generate_timestamp(raw, "Lifetime");
198 gchar *keydata = extract_raw_xml(raw, "saml:Assertion", TRUE);
199 gchar *wsse_security = NULL;
201 if (timestamp && keydata) {
202 gchar *assertionID = extract_raw_xml_attribute(keydata,
203 "AssertionID");
205 if (assertionID) {
206 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
207 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
208 gchar *base64;
209 gchar *signed_info;
210 gchar *canon;
212 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
213 sipe_digest_sha1((guchar *) timestamp,
214 strlen(timestamp),
215 digest);
216 base64 = g_base64_encode(digest,
217 SIPE_DIGEST_SHA1_LENGTH);
219 /* XML-Sig: SignedInfo for reference element */
220 signed_info = g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
221 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
222 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
223 "<Reference URI=\"#timestamp\">"
224 "<Transforms>"
225 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
226 "</Transforms>"
227 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
228 "<DigestValue>%s</DigestValue>"
229 "</Reference>"
230 "</SignedInfo>",
231 base64);
232 g_free(base64);
234 /* XML-Sig: SignedInfo in canonical form */
235 canon = sipe_xml_exc_c14n(signed_info);
236 g_free(signed_info);
238 if (canon) {
239 gchar *signature;
241 /* SignatureValue calculation */
242 /* Temporary. We need to
243 a) extract the wrapped key from keydata
244 b) unwrap the key (kw-aes256)
245 c) use the key as input to HMAC(SHA-1) of canon
248 guchar key[32];
249 (void)entropy;
250 memset(key, 0, sizeof(key));
251 sipe_digest_hmac_sha1(key, sizeof(key),
252 (guchar *)canon,
253 strlen(canon),
254 digest);
256 base64 = g_base64_encode(digest,
257 SIPE_DIGEST_HMAC_SHA1_LENGTH);
259 /* XML-Sig: Signature from SignedInfo + Key */
260 signature = g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
261 " %s"
262 " <SignatureValue>%s</SignatureValue>"
263 " <KeyInfo>"
264 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
265 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
266 " </wsse:SecurityTokenReference>"
267 " </KeyInfo>"
268 "</Signature>",
269 canon,
270 base64,
271 assertionID);
272 g_free(base64);
273 g_free(canon);
275 wsse_security = g_strconcat(timestamp,
276 keydata,
277 signature,
278 NULL);
279 g_free(signature);
282 g_free(assertionID);
286 g_free(keydata);
287 g_free(timestamp);
288 return(wsse_security);
291 static void webticket_token(struct sipe_core_private *sipe_private,
292 const gchar *uri,
293 const gchar *raw,
294 sipe_xml *soap_body,
295 gpointer callback_data)
297 struct certificate_callback_data *ccd = callback_data;
298 gboolean success = (uri == NULL); /* abort case */
300 if (soap_body) {
301 /* WebTicket for Certificate Provisioning Service */
302 if (ccd->webticket_for_certprov) {
303 /* This is a guess: our 256 bits of entropy are used
304 as the private key to wrap the AES key */
305 gchar *wsse_security = generate_sha1_proof_wsse(raw,
306 &ccd->entropy);
308 if (wsse_security) {
310 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
311 uri);
313 success = sipe_svc_get_and_publish_cert(sipe_private,
314 ccd->certprov_uri,
315 ccd->authuser,
316 wsse_security,
317 "", /* TBD.... */
318 get_and_publish_cert,
319 ccd);
320 if (success) {
321 /* callback data passed down the line */
322 ccd = NULL;
324 g_free(wsse_security);
327 /* WebTicket for federated authentication */
328 } else {
329 gchar *wsse_security = generate_fedbearer_wsse(raw);
331 if (wsse_security) {
333 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
334 uri);
336 success = sipe_svc_webticket(sipe_private,
337 ccd->webticket_fedbearer_uri,
338 ccd->authuser,
339 wsse_security,
340 ccd->certprov_uri,
341 &ccd->entropy,
342 webticket_token,
343 ccd);
344 ccd->webticket_for_certprov = TRUE;
346 if (success) {
347 /* callback data passed down the line */
348 ccd = NULL;
350 g_free(wsse_security);
354 } else if (uri) {
355 /* Retry with federated authentication? */
356 success = ccd->webticket_fedbearer_uri && !ccd->tried_fedbearer;
357 if (success) {
358 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
359 uri);
361 ccd->tried_fedbearer = TRUE;
362 success = sipe_svc_webticket_lmc(sipe_private,
363 ccd->authuser,
364 ccd->webticket_fedbearer_uri,
365 webticket_token,
366 ccd);
367 ccd->webticket_for_certprov = FALSE;
369 if (success) {
370 /* callback data passed down the line */
371 ccd = NULL;
376 if (!success) {
377 certificate_failure(sipe_private,
378 _("Web ticket request to %s failed"),
379 uri);
382 callback_data_free(ccd);
385 static void webticket_metadata(struct sipe_core_private *sipe_private,
386 const gchar *uri,
387 SIPE_UNUSED_PARAMETER const gchar *raw,
388 sipe_xml *metadata,
389 gpointer callback_data)
391 struct certificate_callback_data *ccd = callback_data;
393 if (metadata) {
394 const sipe_xml *node;
396 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
397 uri);
399 /* Authentication ports accepted by WebTicket Service */
400 for (node = sipe_xml_child(metadata, "service/port");
401 node;
402 node = sipe_xml_twin(node)) {
403 const gchar *auth_uri = sipe_xml_attribute(sipe_xml_child(node,
404 "address"),
405 "location");
407 if (auth_uri) {
408 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
409 "WebTicketServiceAnon")) {
410 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Anon Auth URI %s", auth_uri);
411 g_free(ccd->webticket_anon_uri);
412 ccd->webticket_anon_uri = g_strdup(auth_uri);
413 } else if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
414 "WsFedBearer")) {
415 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri);
416 g_free(ccd->webticket_fedbearer_uri);
417 ccd->webticket_fedbearer_uri = g_strdup(auth_uri);
422 if (ccd->webticket_anon_uri || ccd->webticket_fedbearer_uri) {
423 gboolean success;
425 if (ccd->webticket_anon_uri) {
426 /* Try anonymous authentication first */
427 /* Entropy: 256 random bits */
428 sipe_svc_fill_random(&ccd->entropy, 256);
430 success = sipe_svc_webticket(sipe_private,
431 ccd->webticket_anon_uri,
432 ccd->authuser,
433 NULL,
434 ccd->certprov_uri,
435 &ccd->entropy,
436 webticket_token,
437 ccd);
438 ccd->webticket_for_certprov = TRUE;
439 } else {
440 ccd->tried_fedbearer = TRUE;
441 success = sipe_svc_webticket_lmc(sipe_private,
442 ccd->authuser,
443 ccd->webticket_fedbearer_uri,
444 webticket_token,
445 ccd);
446 ccd->webticket_for_certprov = FALSE;
449 if (success) {
450 /* callback data passed down the line */
451 ccd = NULL;
452 } else {
453 certificate_failure(sipe_private,
454 _("Can't request security token from %s"),
455 ccd->webticket_anon_uri ? ccd->webticket_anon_uri : ccd->webticket_fedbearer_uri);
458 } else {
459 certificate_failure(sipe_private,
460 _("Can't find the authentication port for TLS-DSK web ticket URI %s"),
461 uri);
464 } else if (uri) {
465 certificate_failure(sipe_private,
466 _("Can't retrieve metadata for TLS-DSK web ticket URI %s"),
467 uri);
470 callback_data_free(ccd);
473 static void certprov_metadata(struct sipe_core_private *sipe_private,
474 const gchar *uri,
475 SIPE_UNUSED_PARAMETER const gchar *raw,
476 sipe_xml *metadata,
477 gpointer callback_data)
479 struct certificate_callback_data *ccd = callback_data;
481 if (metadata) {
482 const sipe_xml *node;
483 gchar *ticket_uri = NULL;
485 SIPE_DEBUG_INFO("certprov_metadata: metadata for service %s retrieved successfully",
486 uri);
488 /* WebTicket policies accepted by Certificate Provisioning Service */
489 for (node = sipe_xml_child(metadata, "Policy");
490 node;
491 node = sipe_xml_twin(node)) {
492 if (sipe_strcase_equal(sipe_xml_attribute(node, "Id"),
493 "CertProvisioningServiceWebTicketProof_SHA1_policy")) {
495 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: WebTicket policy found");
497 ticket_uri = sipe_xml_data(sipe_xml_child(node,
498 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
499 if (ticket_uri) {
500 SIPE_DEBUG_INFO("certprov_metadata: WebTicket URI %s", ticket_uri);
501 } else {
502 certificate_failure(sipe_private,
503 _("Can't find the WebTicket URI for TLS-DSK certificate provisioning URI %s"),
504 uri);
506 break;
510 if (ticket_uri) {
512 /* Authentication ports accepted by Certificate Provisioning Service */
513 for (node = sipe_xml_child(metadata, "service/port");
514 node;
515 node = sipe_xml_twin(node)) {
516 if (sipe_strcase_equal(sipe_xml_attribute(node, "name"),
517 "CertProvisioningServiceWebTicketProof_SHA1")) {
518 const gchar *auth_uri;
520 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: authentication port found");
522 auth_uri = sipe_xml_attribute(sipe_xml_child(node,
523 "address"),
524 "location");
525 if (auth_uri) {
526 SIPE_DEBUG_INFO("certprov_metadata: CertProv Auth URI %s", auth_uri);
528 if (sipe_svc_metadata(sipe_private,
529 ticket_uri,
530 webticket_metadata,
531 ccd)) {
532 /* Remember for later */
533 ccd->certprov_uri = g_strdup(auth_uri);
535 /* callback data passed down the line */
536 ccd = NULL;
537 } else {
538 certificate_failure(sipe_private,
539 _("Can't request metadata from %s"),
540 ticket_uri);
543 break;
547 g_free(ticket_uri);
549 if (!node) {
550 certificate_failure(sipe_private,
551 _("Can't find the authentication port for TLS-DSK certificate provisioning URI %s"),
552 uri);
555 } else {
556 certificate_failure(sipe_private,
557 _("Can't find the WebTicket Policy for TLS-DSK certificate provisioning URI %s"),
558 uri);
561 } else if (uri) {
562 certificate_failure(sipe_private,
563 _("Can't retrieve metadata for TLS-DSK certificate provisioning URI %s"),
564 uri);
567 callback_data_free(ccd);
570 gboolean sipe_certificate_tls_dsk_generate(struct sipe_core_private *sipe_private,
571 const gchar *target,
572 const gchar *authuser,
573 const gchar *uri)
575 struct certificate_callback_data *ccd = g_new0(struct certificate_callback_data, 1);
576 gboolean ret;
578 ccd->target = g_strdup(target);
579 ccd->authuser = g_strdup(authuser);
581 ret = sipe_svc_metadata(sipe_private, uri, certprov_metadata, ccd);
582 if (!ret)
583 callback_data_free(ccd);
585 return(ret);
589 Local Variables:
590 mode: c
591 c-file-style: "bsd"
592 indent-tabs-mode: t
593 tab-width: 8
594 End: