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"
50 #include "sipe-utils.h"
53 struct sipe_certificate
{
54 GHashTable
*certificates
;
55 struct sipe_cert_crypto
*backend
;
58 void sipe_certificate_free(struct sipe_core_private
*sipe_private
)
60 struct sipe_certificate
*sc
= sipe_private
->certificate
;
63 g_hash_table_destroy(sc
->certificates
);
64 sipe_cert_crypto_free(sc
->backend
);
69 static gboolean
sipe_certificate_init(struct sipe_core_private
*sipe_private
)
71 struct sipe_certificate
*sc
;
72 struct sipe_cert_crypto
*ssc
;
74 if (sipe_private
->certificate
)
77 ssc
= sipe_cert_crypto_init();
79 SIPE_DEBUG_ERROR_NOFORMAT("sipe_certificate_init: crypto backend init FAILED!");
83 sc
= g_new0(struct sipe_certificate
, 1);
84 sc
->certificates
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
86 sipe_cert_crypto_destroy
);
89 SIPE_DEBUG_INFO_NOFORMAT("sipe_certificate_init: DONE");
91 sipe_private
->certificate
= sc
;
95 static gchar
*create_certreq(struct sipe_core_private
*sipe_private
,
100 if (!sipe_certificate_init(sipe_private
))
103 SIPE_DEBUG_INFO_NOFORMAT("create_req: generating new certificate request");
105 base64
= sipe_cert_crypto_request(sipe_private
->certificate
->backend
,
108 GString
*format
= g_string_new(NULL
);
109 gsize count
= strlen(base64
);
110 const gchar
*p
= base64
;
112 /* Base64 needs to be formated correctly */
113 #define CERTREQ_BASE64_LINE_LENGTH 76
115 gsize chunk
= count
> CERTREQ_BASE64_LINE_LENGTH
?
116 CERTREQ_BASE64_LINE_LENGTH
: count
;
117 g_string_append_len(format
, p
, chunk
);
118 if (chunk
== CERTREQ_BASE64_LINE_LENGTH
)
119 g_string_append(format
, "\r\n");
124 /* swap Base64 buffers */
126 base64
= format
->str
;
127 g_string_free(format
, FALSE
);
133 static void add_certificate(struct sipe_core_private
*sipe_private
,
135 gpointer certificate
)
137 struct sipe_certificate
*sc
= sipe_private
->certificate
;
138 g_hash_table_insert(sc
->certificates
, g_strdup(target
), certificate
);
141 gpointer
sipe_certificate_tls_dsk_find(struct sipe_core_private
*sipe_private
,
144 struct sipe_certificate
*sc
= sipe_private
->certificate
;
145 gpointer certificate
;
150 certificate
= g_hash_table_lookup(sc
->certificates
, target
);
152 /* Let's make sure the certificate is still valid for another hour */
153 if (!sipe_cert_crypto_valid(certificate
, 60 * 60)) {
154 SIPE_DEBUG_ERROR("sipe_certificate_tls_dsk_find: certificate for '%s' is invalid",
162 struct certificate_callback_data
{
165 gchar
*webticket_anon_uri
;
166 gchar
*webticket_fedbearer_uri
;
169 gboolean tried_fedbearer
;
170 gboolean webticket_for_certprov
;
172 struct sipe_svc_random entropy
;
175 static void callback_data_free(struct certificate_callback_data
*ccd
)
179 g_free(ccd
->authuser
);
180 g_free(ccd
->webticket_anon_uri
);
181 g_free(ccd
->webticket_fedbearer_uri
);
182 g_free(ccd
->certprov_uri
);
183 sipe_svc_free_random(&ccd
->entropy
);
188 static void certificate_failure(struct sipe_core_private
*sipe_private
,
190 const gchar
*parameter
)
192 gchar
*tmp
= g_strdup_printf(format
, parameter
);
193 sipe_backend_connection_error(SIPE_CORE_PUBLIC
,
194 SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED
,
199 static void get_and_publish_cert(struct sipe_core_private
*sipe_private
,
201 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
203 gpointer callback_data
)
205 struct certificate_callback_data
*ccd
= callback_data
;
206 gboolean success
= (uri
== NULL
); /* abort case */
209 gchar
*cert_base64
= sipe_xml_data(sipe_xml_child(soap_body
,
210 "Body/GetAndPublishCertResponse/RequestSecurityTokenResponse/RequestedSecurityToken/BinarySecurityToken"));
212 SIPE_DEBUG_INFO("get_and_publish_cert: received valid SOAP message from service %s",
216 gpointer opaque
= sipe_cert_crypto_decode(sipe_private
->certificate
->backend
,
219 SIPE_DEBUG_INFO_NOFORMAT("get_and_publish_cert: found certificate");
222 add_certificate(sipe_private
,
225 SIPE_DEBUG_INFO("get_and_publish_cert: certificate for target '%s' added",
228 /* Let's try this again... */
229 sip_transport_authentication_completed(sipe_private
);
239 certificate_failure(sipe_private
,
240 _("Certitifcate request to %s failed"),
244 callback_data_free(ccd
);
247 static gchar
*extract_raw_xml_attribute(const gchar
*xml
,
250 gchar
*attr_start
= g_strdup_printf("%s=\"", name
);
252 const gchar
*start
= strstr(xml
, attr_start
);
255 const gchar
*value
= start
+ strlen(attr_start
);
256 const gchar
*end
= strchr(value
, '"');
258 data
= g_strndup(value
, end
- value
);
266 static gchar
*extract_raw_xml(const gchar
*xml
,
268 gboolean include_tag
)
270 gchar
*tag_start
= g_strdup_printf("<%s", tag
);
271 gchar
*tag_end
= g_strdup_printf("</%s>", tag
);
273 const gchar
*start
= strstr(xml
, tag_start
);
276 const gchar
*end
= strstr(start
+ strlen(tag_start
), tag_end
);
279 data
= g_strndup(start
, end
+ strlen(tag_end
) - start
);
281 const gchar
*tmp
= strchr(start
+ strlen(tag_start
), '>') + 1;
282 data
= g_strndup(tmp
, end
- tmp
);
292 static gchar
*generate_timestamp(const gchar
*raw
,
293 const gchar
*lifetime_tag
)
295 gchar
*lifetime
= extract_raw_xml(raw
, lifetime_tag
, FALSE
);
296 gchar
*timestamp
= NULL
;
298 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>",
304 static gchar
*generate_fedbearer_wsse(const gchar
*raw
)
306 gchar
*timestamp
= generate_timestamp(raw
, "wst:Lifetime");
307 gchar
*keydata
= extract_raw_xml(raw
, "EncryptedData", TRUE
);
308 gchar
*wsse_security
= NULL
;
310 if (timestamp
&& keydata
) {
311 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
312 wsse_security
= g_strconcat(timestamp
, keydata
, NULL
);
317 return(wsse_security
);
320 /* P_SHA1() - see RFC2246 "The TLS Protocol Version 1.0", Section 5 */
321 static guchar
*p_sha1(const guchar
*secret
,
327 guchar
*output
= NULL
;
330 * output_length == 0 -> illegal
331 * output_length == 1..20 -> iterations = 1
332 * output_length == 21..40 -> iterations = 2
334 if (secret
&& seed
&& (output_length
> 0)) {
335 guint iterations
= (output_length
+ SIPE_DIGEST_HMAC_SHA1_LENGTH
- 1) / SIPE_DIGEST_HMAC_SHA1_LENGTH
;
336 guchar
*concat
= g_malloc(SIPE_DIGEST_HMAC_SHA1_LENGTH
+ seed_length
);
337 guchar A
[SIPE_DIGEST_HMAC_SHA1_LENGTH
];
340 SIPE_DEBUG_INFO("p_sha1: secret %" G_GSIZE_FORMAT
" bytes, seed %" G_GSIZE_FORMAT
" bytes",
341 secret_length
, seed_length
);
342 SIPE_DEBUG_INFO("p_sha1: output %" G_GSIZE_FORMAT
" bytes -> %d iterations",
343 output_length
, iterations
);
345 /* A(1) = HMAC_SHA1(secret, A(0)), A(0) = seed */
346 sipe_digest_hmac_sha1(secret
, secret_length
,
350 /* Each iteration adds SIPE_DIGEST_HMAC_SHA1_LENGTH bytes */
351 p
= output
= g_malloc(iterations
* SIPE_DIGEST_HMAC_SHA1_LENGTH
);
353 while (iterations
-- > 0) {
354 /* P_SHA1(i) = HMAC_SHA1(secret, A(i) + seed), i = 1, 2, ... */
355 guchar P
[SIPE_DIGEST_HMAC_SHA1_LENGTH
];
356 memcpy(concat
, A
, SIPE_DIGEST_HMAC_SHA1_LENGTH
);
357 memcpy(concat
+ SIPE_DIGEST_HMAC_SHA1_LENGTH
, seed
, seed_length
);
358 sipe_digest_hmac_sha1(secret
, secret_length
,
359 concat
, SIPE_DIGEST_HMAC_SHA1_LENGTH
+ seed_length
,
361 memcpy(p
, P
, SIPE_DIGEST_HMAC_SHA1_LENGTH
);
362 p
+= SIPE_DIGEST_HMAC_SHA1_LENGTH
;
364 /* A(i+1) = HMAC_SHA1(secret, A(i)) */
365 sipe_digest_hmac_sha1(secret
, secret_length
,
366 A
, SIPE_DIGEST_HMAC_SHA1_LENGTH
,
375 static gchar
*generate_sha1_proof_wsse(const gchar
*raw
,
376 struct sipe_svc_random
*entropy
)
378 gchar
*timestamp
= generate_timestamp(raw
, "Lifetime");
379 gchar
*keydata
= extract_raw_xml(raw
, "saml:Assertion", TRUE
);
380 gchar
*wsse_security
= NULL
;
382 if (timestamp
&& keydata
) {
383 gchar
*assertionID
= extract_raw_xml_attribute(keydata
,
389 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
391 * "The key is computed using P_SHA1() from the TLS sepcification to generate
392 * a bit stream using entropy from both sides. The exact form is:
394 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
396 gchar
*entropy_res_base64
= extract_raw_xml(raw
, "BinarySecret", FALSE
);
397 gsize entropy_res_length
;
398 guchar
*entropy_response
= g_base64_decode(entropy_res_base64
,
399 &entropy_res_length
);
400 guchar
*key
= p_sha1(entropy
->buffer
,
405 g_free(entropy_response
);
406 g_free(entropy_res_base64
);
408 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
410 if (assertionID
&& key
) {
411 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
412 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
417 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
419 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
420 sipe_digest_sha1((guchar
*) timestamp
,
423 base64
= g_base64_encode(digest
,
424 SIPE_DIGEST_SHA1_LENGTH
);
426 /* XML-Sig: SignedInfo for reference element */
427 signed_info
= g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
428 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
429 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
430 "<Reference URI=\"#timestamp\">"
432 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
434 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
435 "<DigestValue>%s</DigestValue>"
441 /* XML-Sig: SignedInfo in canonical form */
442 canon
= sipe_xml_exc_c14n(signed_info
);
448 /* calculate signature */
449 sipe_digest_hmac_sha1(key
, entropy
->length
,
453 base64
= g_base64_encode(digest
,
454 SIPE_DIGEST_HMAC_SHA1_LENGTH
);
456 /* XML-Sig: Signature from SignedInfo + Key */
457 signature
= g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
459 " <SignatureValue>%s</SignatureValue>"
461 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
462 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
463 " </wsse:SecurityTokenReference>"
472 wsse_security
= g_strconcat(timestamp
,
487 return(wsse_security
);
490 static void webticket_token(struct sipe_core_private
*sipe_private
,
494 gpointer callback_data
)
496 struct certificate_callback_data
*ccd
= callback_data
;
497 gboolean success
= (uri
== NULL
); /* abort case */
500 /* WebTicket for Certificate Provisioning Service */
501 if (ccd
->webticket_for_certprov
) {
502 gchar
*wsse_security
= generate_sha1_proof_wsse(raw
,
506 gchar
*certreq_base64
= create_certreq(sipe_private
,
509 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
512 if (certreq_base64
) {
514 SIPE_DEBUG_INFO_NOFORMAT("webticket_token: created certificate request");
516 success
= sipe_svc_get_and_publish_cert(sipe_private
,
521 get_and_publish_cert
,
524 /* callback data passed down the line */
527 g_free(certreq_base64
);
529 g_free(wsse_security
);
532 /* WebTicket for federated authentication */
534 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
538 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
541 success
= sipe_svc_webticket(sipe_private
,
542 ccd
->webticket_fedbearer_uri
,
549 ccd
->webticket_for_certprov
= TRUE
;
552 /* callback data passed down the line */
555 g_free(wsse_security
);
560 /* Retry with federated authentication? */
561 success
= ccd
->webticket_fedbearer_uri
&& !ccd
->tried_fedbearer
;
563 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
566 ccd
->tried_fedbearer
= TRUE
;
567 success
= sipe_svc_webticket_lmc(sipe_private
,
569 ccd
->webticket_fedbearer_uri
,
572 ccd
->webticket_for_certprov
= FALSE
;
575 /* callback data passed down the line */
582 certificate_failure(sipe_private
,
583 _("Web ticket request to %s failed"),
587 callback_data_free(ccd
);
590 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
592 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
594 gpointer callback_data
)
596 struct certificate_callback_data
*ccd
= callback_data
;
599 const sipe_xml
*node
;
601 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
604 /* Authentication ports accepted by WebTicket Service */
605 for (node
= sipe_xml_child(metadata
, "service/port");
607 node
= sipe_xml_twin(node
)) {
608 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
613 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
614 "WebTicketServiceAnon")) {
615 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Anon Auth URI %s", auth_uri
);
616 g_free(ccd
->webticket_anon_uri
);
617 ccd
->webticket_anon_uri
= g_strdup(auth_uri
);
618 } else if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
620 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri
);
621 g_free(ccd
->webticket_fedbearer_uri
);
622 ccd
->webticket_fedbearer_uri
= g_strdup(auth_uri
);
627 if (ccd
->webticket_anon_uri
|| ccd
->webticket_fedbearer_uri
) {
630 if (ccd
->webticket_anon_uri
) {
631 /* Try anonymous authentication first */
632 /* Entropy: 256 random bits */
633 sipe_svc_fill_random(&ccd
->entropy
, 256);
635 success
= sipe_svc_webticket(sipe_private
,
636 ccd
->webticket_anon_uri
,
643 ccd
->webticket_for_certprov
= TRUE
;
645 ccd
->tried_fedbearer
= TRUE
;
646 success
= sipe_svc_webticket_lmc(sipe_private
,
648 ccd
->webticket_fedbearer_uri
,
651 ccd
->webticket_for_certprov
= FALSE
;
655 /* callback data passed down the line */
658 certificate_failure(sipe_private
,
659 _("Can't request security token from %s"),
660 ccd
->webticket_anon_uri
? ccd
->webticket_anon_uri
: ccd
->webticket_fedbearer_uri
);
664 certificate_failure(sipe_private
,
665 _("Can't find the authentication port for TLS-DSK web ticket URI %s"),
670 certificate_failure(sipe_private
,
671 _("Can't retrieve metadata for TLS-DSK web ticket URI %s"),
675 callback_data_free(ccd
);
678 static void certprov_metadata(struct sipe_core_private
*sipe_private
,
680 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
682 gpointer callback_data
)
684 struct certificate_callback_data
*ccd
= callback_data
;
687 const sipe_xml
*node
;
688 gchar
*ticket_uri
= NULL
;
690 SIPE_DEBUG_INFO("certprov_metadata: metadata for service %s retrieved successfully",
693 /* WebTicket policies accepted by Certificate Provisioning Service */
694 for (node
= sipe_xml_child(metadata
, "Policy");
696 node
= sipe_xml_twin(node
)) {
697 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
698 "CertProvisioningServiceWebTicketProof_SHA1_policy")) {
700 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: WebTicket policy found");
702 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
703 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
705 SIPE_DEBUG_INFO("certprov_metadata: WebTicket URI %s", ticket_uri
);
707 certificate_failure(sipe_private
,
708 _("Can't find the WebTicket URI for TLS-DSK certificate provisioning URI %s"),
717 /* Authentication ports accepted by Certificate Provisioning Service */
718 for (node
= sipe_xml_child(metadata
, "service/port");
720 node
= sipe_xml_twin(node
)) {
721 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
722 "CertProvisioningServiceWebTicketProof_SHA1")) {
723 const gchar
*auth_uri
;
725 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: authentication port found");
727 auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
731 SIPE_DEBUG_INFO("certprov_metadata: CertProv Auth URI %s", auth_uri
);
733 if (sipe_svc_metadata(sipe_private
,
737 /* Remember for later */
738 ccd
->certprov_uri
= g_strdup(auth_uri
);
740 /* callback data passed down the line */
743 certificate_failure(sipe_private
,
744 _("Can't request metadata from %s"),
755 certificate_failure(sipe_private
,
756 _("Can't find the authentication port for TLS-DSK certificate provisioning URI %s"),
761 certificate_failure(sipe_private
,
762 _("Can't find the WebTicket Policy for TLS-DSK certificate provisioning URI %s"),
767 certificate_failure(sipe_private
,
768 _("Can't retrieve metadata for TLS-DSK certificate provisioning URI %s"),
772 callback_data_free(ccd
);
775 gboolean
sipe_certificate_tls_dsk_generate(struct sipe_core_private
*sipe_private
,
777 const gchar
*authuser
,
780 struct certificate_callback_data
*ccd
= g_new0(struct certificate_callback_data
, 1);
783 ccd
->target
= g_strdup(target
);
784 ccd
->authuser
= g_strdup(authuser
);
786 ret
= sipe_svc_metadata(sipe_private
, uri
, certprov_metadata
, ccd
);
788 callback_data_free(ccd
);