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 "sip-transport.h"
42 #include "sipe-backend.h"
43 #include "sipe-core.h"
44 #include "sipe-core-private.h"
45 #include "sipe-certificate.h"
46 #include "sipe-cert-crypto.h"
47 #include "sipe-digest.h"
51 #include "sipe-utils.h"
54 struct sipe_certificate
{
55 GHashTable
*certificates
;
56 struct sipe_cert_crypto
*backend
;
59 void sipe_certificate_free(struct sipe_core_private
*sipe_private
)
61 struct sipe_certificate
*sc
= sipe_private
->certificate
;
64 g_hash_table_destroy(sc
->certificates
);
65 sipe_cert_crypto_free(sc
->backend
);
70 static gboolean
sipe_certificate_init(struct sipe_core_private
*sipe_private
)
72 struct sipe_certificate
*sc
;
73 struct sipe_cert_crypto
*ssc
;
75 if (sipe_private
->certificate
)
78 ssc
= sipe_cert_crypto_init();
80 SIPE_DEBUG_ERROR_NOFORMAT("sipe_certificate_init: crypto backend init FAILED!");
84 sc
= g_new0(struct sipe_certificate
, 1);
85 sc
->certificates
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
87 sipe_cert_crypto_destroy
);
90 SIPE_DEBUG_INFO_NOFORMAT("sipe_certificate_init: DONE");
92 sipe_private
->certificate
= sc
;
96 static gchar
*create_certreq(struct sipe_core_private
*sipe_private
,
101 if (!sipe_certificate_init(sipe_private
))
104 SIPE_DEBUG_INFO_NOFORMAT("create_req: generating new certificate request");
106 base64
= sipe_cert_crypto_request(sipe_private
->certificate
->backend
,
109 GString
*format
= g_string_new(NULL
);
110 gsize count
= strlen(base64
);
111 const gchar
*p
= base64
;
113 /* Base64 needs to be formated correctly */
114 #define CERTREQ_BASE64_LINE_LENGTH 76
116 gsize chunk
= count
> CERTREQ_BASE64_LINE_LENGTH
?
117 CERTREQ_BASE64_LINE_LENGTH
: count
;
118 g_string_append_len(format
, p
, chunk
);
119 if (chunk
== CERTREQ_BASE64_LINE_LENGTH
)
120 g_string_append(format
, "\r\n");
125 /* swap Base64 buffers */
127 base64
= format
->str
;
128 g_string_free(format
, FALSE
);
134 static void add_certificate(struct sipe_core_private
*sipe_private
,
136 gpointer certificate
)
138 struct sipe_certificate
*sc
= sipe_private
->certificate
;
139 g_hash_table_insert(sc
->certificates
, g_strdup(target
), certificate
);
142 gpointer
sipe_certificate_tls_dsk_find(struct sipe_core_private
*sipe_private
,
145 struct sipe_certificate
*sc
= sipe_private
->certificate
;
146 gpointer certificate
;
151 certificate
= g_hash_table_lookup(sc
->certificates
, target
);
153 /* Let's make sure the certificate is still valid for another hour */
154 if (!sipe_cert_crypto_valid(certificate
, 60 * 60)) {
155 SIPE_DEBUG_ERROR("sipe_certificate_tls_dsk_find: certificate for '%s' is invalid",
163 struct certificate_callback_data
{
166 gchar
*webticket_negotiate_uri
;
167 gchar
*webticket_fedbearer_uri
;
170 gboolean tried_fedbearer
;
171 gboolean webticket_for_certprov
;
173 struct sipe_tls_random entropy
;
176 static void callback_data_free(struct certificate_callback_data
*ccd
)
180 g_free(ccd
->authuser
);
181 g_free(ccd
->webticket_negotiate_uri
);
182 g_free(ccd
->webticket_fedbearer_uri
);
183 g_free(ccd
->certprov_uri
);
184 sipe_tls_free_random(&ccd
->entropy
);
189 static void certificate_failure(struct sipe_core_private
*sipe_private
,
191 const gchar
*parameter
)
193 gchar
*tmp
= g_strdup_printf(format
, parameter
);
194 sipe_backend_connection_error(SIPE_CORE_PUBLIC
,
195 SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED
,
200 static void get_and_publish_cert(struct sipe_core_private
*sipe_private
,
202 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
204 gpointer callback_data
)
206 struct certificate_callback_data
*ccd
= callback_data
;
207 gboolean success
= (uri
== NULL
); /* abort case */
210 gchar
*cert_base64
= sipe_xml_data(sipe_xml_child(soap_body
,
211 "Body/GetAndPublishCertResponse/RequestSecurityTokenResponse/RequestedSecurityToken/BinarySecurityToken"));
213 SIPE_DEBUG_INFO("get_and_publish_cert: received valid SOAP message from service %s",
217 gpointer opaque
= sipe_cert_crypto_decode(sipe_private
->certificate
->backend
,
220 SIPE_DEBUG_INFO_NOFORMAT("get_and_publish_cert: found certificate");
223 add_certificate(sipe_private
,
226 SIPE_DEBUG_INFO("get_and_publish_cert: certificate for target '%s' added",
229 /* Let's try this again... */
230 sip_transport_authentication_completed(sipe_private
);
240 certificate_failure(sipe_private
,
241 _("Certificate request to %s failed"),
245 callback_data_free(ccd
);
248 static gchar
*extract_raw_xml_attribute(const gchar
*xml
,
251 gchar
*attr_start
= g_strdup_printf("%s=\"", name
);
253 const gchar
*start
= strstr(xml
, attr_start
);
256 const gchar
*value
= start
+ strlen(attr_start
);
257 const gchar
*end
= strchr(value
, '"');
259 data
= g_strndup(value
, end
- value
);
267 static gchar
*extract_raw_xml(const gchar
*xml
,
269 gboolean include_tag
)
271 gchar
*tag_start
= g_strdup_printf("<%s", tag
);
272 gchar
*tag_end
= g_strdup_printf("</%s>", tag
);
274 const gchar
*start
= strstr(xml
, tag_start
);
277 const gchar
*end
= strstr(start
+ strlen(tag_start
), tag_end
);
280 data
= g_strndup(start
, end
+ strlen(tag_end
) - start
);
282 const gchar
*tmp
= strchr(start
+ strlen(tag_start
), '>') + 1;
283 data
= g_strndup(tmp
, end
- tmp
);
293 static gchar
*generate_timestamp(const gchar
*raw
,
294 const gchar
*lifetime_tag
)
296 gchar
*lifetime
= extract_raw_xml(raw
, lifetime_tag
, FALSE
);
297 gchar
*timestamp
= NULL
;
299 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>",
305 static gchar
*generate_fedbearer_wsse(const gchar
*raw
)
307 gchar
*timestamp
= generate_timestamp(raw
, "wst:Lifetime");
308 gchar
*keydata
= extract_raw_xml(raw
, "EncryptedData", TRUE
);
309 gchar
*wsse_security
= NULL
;
311 if (timestamp
&& keydata
) {
312 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
313 wsse_security
= g_strconcat(timestamp
, keydata
, NULL
);
318 return(wsse_security
);
321 static gchar
*generate_sha1_proof_wsse(const gchar
*raw
,
322 struct sipe_tls_random
*entropy
)
324 gchar
*timestamp
= generate_timestamp(raw
, "Lifetime");
325 gchar
*keydata
= extract_raw_xml(raw
, "saml:Assertion", TRUE
);
326 gchar
*wsse_security
= NULL
;
328 if (timestamp
&& keydata
) {
329 gchar
*assertionID
= extract_raw_xml_attribute(keydata
,
335 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
337 * "The key is computed using P_SHA1() from the TLS sepcification to generate
338 * a bit stream using entropy from both sides. The exact form is:
340 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
342 gchar
*entropy_res_base64
= extract_raw_xml(raw
, "BinarySecret", FALSE
);
343 gsize entropy_res_length
;
344 guchar
*entropy_response
= g_base64_decode(entropy_res_base64
,
345 &entropy_res_length
);
346 guchar
*key
= sipe_tls_p_sha1(entropy
->buffer
,
351 g_free(entropy_response
);
352 g_free(entropy_res_base64
);
354 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
356 if (assertionID
&& key
) {
357 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
358 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
363 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
365 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
366 sipe_digest_sha1((guchar
*) timestamp
,
369 base64
= g_base64_encode(digest
,
370 SIPE_DIGEST_SHA1_LENGTH
);
372 /* XML-Sig: SignedInfo for reference element */
373 signed_info
= g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
374 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
375 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
376 "<Reference URI=\"#timestamp\">"
378 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
380 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
381 "<DigestValue>%s</DigestValue>"
387 /* XML-Sig: SignedInfo in canonical form */
388 canon
= sipe_xml_exc_c14n(signed_info
);
394 /* calculate signature */
395 sipe_digest_hmac_sha1(key
, entropy
->length
,
399 base64
= g_base64_encode(digest
,
400 SIPE_DIGEST_HMAC_SHA1_LENGTH
);
402 /* XML-Sig: Signature from SignedInfo + Key */
403 signature
= g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
405 " <SignatureValue>%s</SignatureValue>"
407 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
408 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
409 " </wsse:SecurityTokenReference>"
418 wsse_security
= g_strconcat(timestamp
,
433 return(wsse_security
);
436 static void webticket_token(struct sipe_core_private
*sipe_private
,
440 gpointer callback_data
)
442 struct certificate_callback_data
*ccd
= callback_data
;
443 gboolean success
= (uri
== NULL
); /* abort case */
446 /* WebTicket for Certificate Provisioning Service */
447 if (ccd
->webticket_for_certprov
) {
448 gchar
*wsse_security
= generate_sha1_proof_wsse(raw
,
452 gchar
*certreq_base64
= create_certreq(sipe_private
,
455 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
458 if (certreq_base64
) {
460 SIPE_DEBUG_INFO_NOFORMAT("webticket_token: created certificate request");
462 success
= sipe_svc_get_and_publish_cert(sipe_private
,
467 get_and_publish_cert
,
470 /* callback data passed down the line */
473 g_free(certreq_base64
);
475 g_free(wsse_security
);
478 /* WebTicket for federated authentication */
480 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
484 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
487 success
= sipe_svc_webticket(sipe_private
,
488 ccd
->webticket_fedbearer_uri
,
495 ccd
->webticket_for_certprov
= TRUE
;
498 /* callback data passed down the line */
501 g_free(wsse_security
);
506 /* Retry with federated authentication? */
507 success
= ccd
->webticket_fedbearer_uri
&& !ccd
->tried_fedbearer
;
509 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
512 ccd
->tried_fedbearer
= TRUE
;
513 success
= sipe_svc_webticket_lmc(sipe_private
,
515 ccd
->webticket_fedbearer_uri
,
518 ccd
->webticket_for_certprov
= FALSE
;
521 /* callback data passed down the line */
528 certificate_failure(sipe_private
,
529 _("Web ticket request to %s failed"),
533 callback_data_free(ccd
);
536 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
538 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
540 gpointer callback_data
)
542 struct certificate_callback_data
*ccd
= callback_data
;
545 const sipe_xml
*node
;
547 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
550 /* Authentication ports accepted by WebTicket Service */
551 for (node
= sipe_xml_child(metadata
, "service/port");
553 node
= sipe_xml_twin(node
)) {
554 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
559 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
560 "WebTicketServiceWinNegotiate")) {
561 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri
);
562 g_free(ccd
->webticket_negotiate_uri
);
563 ccd
->webticket_negotiate_uri
= g_strdup(auth_uri
);
564 } else if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
566 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri
);
567 g_free(ccd
->webticket_fedbearer_uri
);
568 ccd
->webticket_fedbearer_uri
= g_strdup(auth_uri
);
573 if (ccd
->webticket_negotiate_uri
|| ccd
->webticket_fedbearer_uri
) {
576 /* Entropy: 256 random bits */
577 if (!ccd
->entropy
.buffer
)
578 sipe_tls_fill_random(&ccd
->entropy
, 256);
580 if (ccd
->webticket_negotiate_uri
) {
581 /* Try Negotiate authentication first */
583 success
= sipe_svc_webticket(sipe_private
,
584 ccd
->webticket_negotiate_uri
,
591 ccd
->webticket_for_certprov
= TRUE
;
593 ccd
->tried_fedbearer
= TRUE
;
594 success
= sipe_svc_webticket_lmc(sipe_private
,
596 ccd
->webticket_fedbearer_uri
,
599 ccd
->webticket_for_certprov
= FALSE
;
603 /* callback data passed down the line */
606 certificate_failure(sipe_private
,
607 _("Can't request security token from %s"),
608 ccd
->webticket_negotiate_uri
? ccd
->webticket_negotiate_uri
: ccd
->webticket_fedbearer_uri
);
612 certificate_failure(sipe_private
,
613 _("Can't find the authentication port for TLS-DSK web ticket URI %s"),
618 certificate_failure(sipe_private
,
619 _("Can't retrieve metadata for TLS-DSK web ticket URI %s"),
623 callback_data_free(ccd
);
626 static void certprov_metadata(struct sipe_core_private
*sipe_private
,
628 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
630 gpointer callback_data
)
632 struct certificate_callback_data
*ccd
= callback_data
;
635 const sipe_xml
*node
;
636 gchar
*ticket_uri
= NULL
;
638 SIPE_DEBUG_INFO("certprov_metadata: metadata for service %s retrieved successfully",
641 /* WebTicket policies accepted by Certificate Provisioning Service */
642 for (node
= sipe_xml_child(metadata
, "Policy");
644 node
= sipe_xml_twin(node
)) {
645 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
646 "CertProvisioningServiceWebTicketProof_SHA1_policy")) {
648 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: WebTicket policy found");
650 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
651 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
653 SIPE_DEBUG_INFO("certprov_metadata: WebTicket URI %s", ticket_uri
);
655 certificate_failure(sipe_private
,
656 _("Can't find the web ticket URI for TLS-DSK certificate provisioning URI %s"),
665 /* Authentication ports accepted by Certificate Provisioning Service */
666 for (node
= sipe_xml_child(metadata
, "service/port");
668 node
= sipe_xml_twin(node
)) {
669 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
670 "CertProvisioningServiceWebTicketProof_SHA1")) {
671 const gchar
*auth_uri
;
673 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: authentication port found");
675 auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
679 SIPE_DEBUG_INFO("certprov_metadata: CertProv Auth URI %s", auth_uri
);
681 if (sipe_svc_metadata(sipe_private
,
685 /* Remember for later */
686 ccd
->certprov_uri
= g_strdup(auth_uri
);
688 /* callback data passed down the line */
691 certificate_failure(sipe_private
,
692 _("Can't request metadata from %s"),
703 certificate_failure(sipe_private
,
704 _("Can't find the authentication port for TLS-DSK certificate provisioning URI %s"),
709 certificate_failure(sipe_private
,
710 _("Can't find the web ticket policy for TLS-DSK certificate provisioning URI %s"),
715 certificate_failure(sipe_private
,
716 _("Can't retrieve metadata for TLS-DSK certificate provisioning URI %s"),
720 callback_data_free(ccd
);
723 gboolean
sipe_certificate_tls_dsk_generate(struct sipe_core_private
*sipe_private
,
725 const gchar
*authuser
,
728 struct certificate_callback_data
*ccd
= g_new0(struct certificate_callback_data
, 1);
731 ccd
->target
= g_strdup(target
);
732 ccd
->authuser
= g_strdup(authuser
);
734 ret
= sipe_svc_metadata(sipe_private
, uri
, certprov_metadata
, ccd
);
736 callback_data_free(ccd
);