2 * @file sipe-certificate.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-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
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"
48 #include "sipe-utils.h"
51 struct certificate_callback_data
{
54 gchar
*webticket_anon_uri
;
55 gchar
*webticket_fedbearer_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
)
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
);
77 gpointer
sipe_certificate_tls_dsk_find(struct sipe_core_private
*sipe_private
,
89 static void certificate_failure(struct sipe_core_private
*sipe_private
,
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
,
100 static void get_and_publish_cert(struct sipe_core_private
*sipe_private
,
104 gpointer callback_data
)
106 struct certificate_callback_data
*ccd
= callback_data
;
114 certificate_failure(sipe_private
,
115 _("Certitifcate request to %s failed"),
119 callback_data_free(ccd
);
122 static gchar
*extract_raw_xml_attribute(const gchar
*xml
,
125 gchar
*attr_start
= g_strdup_printf("%s=\"", name
);
127 const gchar
*start
= strstr(xml
, attr_start
);
130 const gchar
*value
= start
+ strlen(attr_start
);
131 const gchar
*end
= strchr(value
, '"');
133 data
= g_strndup(value
, end
- value
);
141 static gchar
*extract_raw_xml(const gchar
*xml
,
143 gboolean include_tag
)
145 gchar
*tag_start
= g_strdup_printf("<%s", tag
);
146 gchar
*tag_end
= g_strdup_printf("</%s>", tag
);
148 const gchar
*start
= strstr(xml
, tag_start
);
151 const gchar
*end
= strstr(start
+ strlen(tag_start
), tag_end
);
154 data
= g_strndup(start
, end
+ strlen(tag_end
) - start
);
156 const gchar
*tmp
= strchr(start
+ strlen(tag_start
), '>') + 1;
157 data
= g_strndup(tmp
, end
- tmp
);
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
;
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>",
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
);
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
,
206 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
207 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
212 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
213 sipe_digest_sha1((guchar
*) timestamp
,
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\">"
225 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
227 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
228 "<DigestValue>%s</DigestValue>"
234 /* XML-Sig: SignedInfo in canonical form */
235 canon
= sipe_xml_exc_c14n(signed_info
);
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
250 memset(key
, 0, sizeof(key
));
251 sipe_digest_hmac_sha1(key
, sizeof(key
),
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#\">"
262 " <SignatureValue>%s</SignatureValue>"
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>"
275 wsse_security
= g_strconcat(timestamp
,
288 return(wsse_security
);
291 static void webticket_token(struct sipe_core_private
*sipe_private
,
295 gpointer callback_data
)
297 struct certificate_callback_data
*ccd
= callback_data
;
298 gboolean success
= (uri
== NULL
); /* abort case */
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
,
310 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
313 success
= sipe_svc_get_and_publish_cert(sipe_private
,
318 get_and_publish_cert
,
321 /* callback data passed down the line */
324 g_free(wsse_security
);
327 /* WebTicket for federated authentication */
329 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
333 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
336 success
= sipe_svc_webticket(sipe_private
,
337 ccd
->webticket_fedbearer_uri
,
344 ccd
->webticket_for_certprov
= TRUE
;
347 /* callback data passed down the line */
350 g_free(wsse_security
);
355 /* Retry with federated authentication? */
356 success
= ccd
->webticket_fedbearer_uri
&& !ccd
->tried_fedbearer
;
358 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
361 ccd
->tried_fedbearer
= TRUE
;
362 success
= sipe_svc_webticket_lmc(sipe_private
,
364 ccd
->webticket_fedbearer_uri
,
367 ccd
->webticket_for_certprov
= FALSE
;
370 /* callback data passed down the line */
377 certificate_failure(sipe_private
,
378 _("Web ticket request to %s failed"),
382 callback_data_free(ccd
);
385 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
387 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
389 gpointer callback_data
)
391 struct certificate_callback_data
*ccd
= 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 "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"),
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
) {
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
,
438 ccd
->webticket_for_certprov
= TRUE
;
440 ccd
->tried_fedbearer
= TRUE
;
441 success
= sipe_svc_webticket_lmc(sipe_private
,
443 ccd
->webticket_fedbearer_uri
,
446 ccd
->webticket_for_certprov
= FALSE
;
450 /* callback data passed down the line */
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
);
459 certificate_failure(sipe_private
,
460 _("Can't find the authentication port for TLS-DSK web ticket URI %s"),
465 certificate_failure(sipe_private
,
466 _("Can't retrieve metadata for TLS-DSK web ticket URI %s"),
470 callback_data_free(ccd
);
473 static void certprov_metadata(struct sipe_core_private
*sipe_private
,
475 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
477 gpointer callback_data
)
479 struct certificate_callback_data
*ccd
= callback_data
;
482 const sipe_xml
*node
;
483 gchar
*ticket_uri
= NULL
;
485 SIPE_DEBUG_INFO("certprov_metadata: metadata for service %s retrieved successfully",
488 /* WebTicket policies accepted by Certificate Provisioning Service */
489 for (node
= sipe_xml_child(metadata
, "Policy");
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"));
500 SIPE_DEBUG_INFO("certprov_metadata: WebTicket URI %s", ticket_uri
);
502 certificate_failure(sipe_private
,
503 _("Can't find the WebTicket URI for TLS-DSK certificate provisioning URI %s"),
512 /* Authentication ports accepted by Certificate Provisioning Service */
513 for (node
= sipe_xml_child(metadata
, "service/port");
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
,
526 SIPE_DEBUG_INFO("certprov_metadata: CertProv Auth URI %s", auth_uri
);
528 if (sipe_svc_metadata(sipe_private
,
532 /* Remember for later */
533 ccd
->certprov_uri
= g_strdup(auth_uri
);
535 /* callback data passed down the line */
538 certificate_failure(sipe_private
,
539 _("Can't request metadata from %s"),
550 certificate_failure(sipe_private
,
551 _("Can't find the authentication port for TLS-DSK certificate provisioning URI %s"),
556 certificate_failure(sipe_private
,
557 _("Can't find the WebTicket Policy for TLS-DSK certificate provisioning URI %s"),
562 certificate_failure(sipe_private
,
563 _("Can't retrieve metadata for TLS-DSK certificate provisioning URI %s"),
567 callback_data_free(ccd
);
570 gboolean
sipe_certificate_tls_dsk_generate(struct sipe_core_private
*sipe_private
,
572 const gchar
*authuser
,
575 struct certificate_callback_data
*ccd
= g_new0(struct certificate_callback_data
, 1);
578 ccd
->target
= g_strdup(target
);
579 ccd
->authuser
= g_strdup(authuser
);
581 ret
= sipe_svc_metadata(sipe_private
, uri
, certprov_metadata
, ccd
);
583 callback_data_free(ccd
);