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-cert-crypto.h"
46 #include "sipe-digest.h"
49 #include "sipe-utils.h"
52 struct sipe_certificate
{
53 GHashTable
*certificates
;
54 struct sipe_cert_crypto
*backend
;
57 void sipe_certificate_free(struct sipe_core_private
*sipe_private
)
59 struct sipe_certificate
*sc
= sipe_private
->certificate
;
62 g_hash_table_destroy(sc
->certificates
);
63 sipe_cert_crypto_free(sc
->backend
);
68 static gboolean
sipe_certificate_init(struct sipe_core_private
*sipe_private
)
70 struct sipe_certificate
*sc
;
71 struct sipe_cert_crypto
*ssc
;
73 if (sipe_private
->certificate
)
76 ssc
= sipe_cert_crypto_init();
78 SIPE_DEBUG_ERROR_NOFORMAT("sipe_certificate_init: crypto backend init FAILED!");
82 sc
= g_new0(struct sipe_certificate
, 1);
83 sc
->certificates
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
85 sipe_cert_crypto_destroy
);
88 SIPE_DEBUG_INFO_NOFORMAT("sipe_certificate_init: DONE");
90 sipe_private
->certificate
= sc
;
94 static gchar
*create_certreq(struct sipe_core_private
*sipe_private
,
97 if (!sipe_certificate_init(sipe_private
))
100 return(sipe_cert_crypto_request(sipe_private
->certificate
->backend
,
104 gpointer
sipe_certificate_tls_dsk_find(struct sipe_core_private
*sipe_private
,
107 struct sipe_certificate
*sc
= sipe_private
->certificate
;
112 return(g_hash_table_lookup(sc
->certificates
, target
));
115 struct certificate_callback_data
{
118 gchar
*webticket_anon_uri
;
119 gchar
*webticket_fedbearer_uri
;
122 gboolean tried_fedbearer
;
123 gboolean webticket_for_certprov
;
125 struct sipe_svc_random entropy
;
128 static void callback_data_free(struct certificate_callback_data
*ccd
)
132 g_free(ccd
->authuser
);
133 g_free(ccd
->webticket_anon_uri
);
134 g_free(ccd
->webticket_fedbearer_uri
);
135 g_free(ccd
->certprov_uri
);
136 sipe_svc_free_random(&ccd
->entropy
);
141 static void certificate_failure(struct sipe_core_private
*sipe_private
,
143 const gchar
*parameter
)
145 gchar
*tmp
= g_strdup_printf(format
, parameter
);
146 sipe_backend_connection_error(SIPE_CORE_PUBLIC
,
147 SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED
,
152 static void get_and_publish_cert(struct sipe_core_private
*sipe_private
,
156 gpointer callback_data
)
158 struct certificate_callback_data
*ccd
= callback_data
;
162 SIPE_DEBUG_INFO("get_and_publish_cert: received valid SOAP message from service %s",
169 certificate_failure(sipe_private
,
170 _("Certitifcate request to %s failed"),
174 callback_data_free(ccd
);
177 static gchar
*extract_raw_xml_attribute(const gchar
*xml
,
180 gchar
*attr_start
= g_strdup_printf("%s=\"", name
);
182 const gchar
*start
= strstr(xml
, attr_start
);
185 const gchar
*value
= start
+ strlen(attr_start
);
186 const gchar
*end
= strchr(value
, '"');
188 data
= g_strndup(value
, end
- value
);
196 static gchar
*extract_raw_xml(const gchar
*xml
,
198 gboolean include_tag
)
200 gchar
*tag_start
= g_strdup_printf("<%s", tag
);
201 gchar
*tag_end
= g_strdup_printf("</%s>", tag
);
203 const gchar
*start
= strstr(xml
, tag_start
);
206 const gchar
*end
= strstr(start
+ strlen(tag_start
), tag_end
);
209 data
= g_strndup(start
, end
+ strlen(tag_end
) - start
);
211 const gchar
*tmp
= strchr(start
+ strlen(tag_start
), '>') + 1;
212 data
= g_strndup(tmp
, end
- tmp
);
222 static gchar
*generate_timestamp(const gchar
*raw
,
223 const gchar
*lifetime_tag
)
225 gchar
*lifetime
= extract_raw_xml(raw
, lifetime_tag
, FALSE
);
226 gchar
*timestamp
= NULL
;
228 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>",
234 static gchar
*generate_fedbearer_wsse(const gchar
*raw
)
236 gchar
*timestamp
= generate_timestamp(raw
, "wst:Lifetime");
237 gchar
*keydata
= extract_raw_xml(raw
, "EncryptedData", TRUE
);
238 gchar
*wsse_security
= NULL
;
240 if (timestamp
&& keydata
) {
241 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
242 wsse_security
= g_strconcat(timestamp
, keydata
, NULL
);
247 return(wsse_security
);
250 /* P_SHA1() - see RFC2246 "The TLS Protocol Version 1.0", Section 5 */
251 static guchar
*p_sha1(const guchar
*secret
,
257 guchar
*output
= NULL
;
260 * output_length == 0 -> illegal
261 * output_length == 1..20 -> iterations = 1
262 * output_length == 21..40 -> iterations = 2
264 if (secret
&& seed
&& (output_length
> 0)) {
265 guint iterations
= (output_length
+ SIPE_DIGEST_HMAC_SHA1_LENGTH
- 1) / SIPE_DIGEST_HMAC_SHA1_LENGTH
;
266 guchar
*concat
= g_malloc(SIPE_DIGEST_HMAC_SHA1_LENGTH
+ seed_length
);
267 guchar A
[SIPE_DIGEST_HMAC_SHA1_LENGTH
];
270 SIPE_DEBUG_INFO("p_sha1: secret %" G_GSIZE_FORMAT
" bytes, seed %" G_GSIZE_FORMAT
" bytes",
271 secret_length
, seed_length
);
272 SIPE_DEBUG_INFO("p_sha1: output %" G_GSIZE_FORMAT
" bytes -> %d iterations",
273 output_length
, iterations
);
275 /* A(1) = HMAC_SHA1(secret, A(0)), A(0) = seed */
276 sipe_digest_hmac_sha1(secret
, secret_length
,
280 /* Each iteration adds SIPE_DIGEST_HMAC_SHA1_LENGTH bytes */
281 p
= output
= g_malloc(iterations
* SIPE_DIGEST_HMAC_SHA1_LENGTH
);
283 while (iterations
-- > 0) {
284 /* P_SHA1(i) = HMAC_SHA1(secret, A(i) + seed), i = 1, 2, ... */
285 guchar P
[SIPE_DIGEST_HMAC_SHA1_LENGTH
];
286 memcpy(concat
, A
, SIPE_DIGEST_HMAC_SHA1_LENGTH
);
287 memcpy(concat
+ SIPE_DIGEST_HMAC_SHA1_LENGTH
, seed
, seed_length
);
288 sipe_digest_hmac_sha1(secret
, secret_length
,
289 concat
, SIPE_DIGEST_HMAC_SHA1_LENGTH
+ seed_length
,
291 memcpy(p
, P
, SIPE_DIGEST_HMAC_SHA1_LENGTH
);
292 p
+= SIPE_DIGEST_HMAC_SHA1_LENGTH
;
294 /* A(i+1) = HMAC_SHA1(secret, A(i)) */
295 sipe_digest_hmac_sha1(secret
, secret_length
,
296 A
, SIPE_DIGEST_HMAC_SHA1_LENGTH
,
305 static gchar
*generate_sha1_proof_wsse(const gchar
*raw
,
306 struct sipe_svc_random
*entropy
)
308 gchar
*timestamp
= generate_timestamp(raw
, "Lifetime");
309 gchar
*keydata
= extract_raw_xml(raw
, "saml:Assertion", TRUE
);
310 gchar
*wsse_security
= NULL
;
312 if (timestamp
&& keydata
) {
313 gchar
*assertionID
= extract_raw_xml_attribute(keydata
,
319 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
321 * "The key is computed using P_SHA1() from the TLS sepcification to generate
322 * a bit stream using entropy from both sides. The exact form is:
324 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
326 gchar
*entropy_res_base64
= extract_raw_xml(raw
, "BinarySecret", FALSE
);
327 gsize entropy_res_length
;
328 guchar
*entropy_response
= g_base64_decode(entropy_res_base64
,
329 &entropy_res_length
);
330 guchar
*key
= p_sha1(entropy
->buffer
,
335 g_free(entropy_response
);
336 g_free(entropy_res_base64
);
338 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
340 if (assertionID
&& key
) {
341 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
342 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
347 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
349 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
350 sipe_digest_sha1((guchar
*) timestamp
,
353 base64
= g_base64_encode(digest
,
354 SIPE_DIGEST_SHA1_LENGTH
);
356 /* XML-Sig: SignedInfo for reference element */
357 signed_info
= g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
358 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
359 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
360 "<Reference URI=\"#timestamp\">"
362 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
364 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
365 "<DigestValue>%s</DigestValue>"
371 /* XML-Sig: SignedInfo in canonical form */
372 canon
= sipe_xml_exc_c14n(signed_info
);
378 /* calculate signature */
379 sipe_digest_hmac_sha1(key
, entropy
->length
,
383 base64
= g_base64_encode(digest
,
384 SIPE_DIGEST_HMAC_SHA1_LENGTH
);
386 /* XML-Sig: Signature from SignedInfo + Key */
387 signature
= g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
389 " <SignatureValue>%s</SignatureValue>"
391 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
392 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
393 " </wsse:SecurityTokenReference>"
402 wsse_security
= g_strconcat(timestamp
,
417 return(wsse_security
);
420 static void webticket_token(struct sipe_core_private
*sipe_private
,
424 gpointer callback_data
)
426 struct certificate_callback_data
*ccd
= callback_data
;
427 gboolean success
= (uri
== NULL
); /* abort case */
430 /* WebTicket for Certificate Provisioning Service */
431 if (ccd
->webticket_for_certprov
) {
432 gchar
*wsse_security
= generate_sha1_proof_wsse(raw
,
436 gchar
*certreq_base64
= create_certreq(sipe_private
,
439 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
442 if (certreq_base64
) {
444 SIPE_DEBUG_INFO_NOFORMAT("webticket_token: created certificate request");
446 success
= sipe_svc_get_and_publish_cert(sipe_private
,
451 get_and_publish_cert
,
454 /* callback data passed down the line */
457 g_free(certreq_base64
);
459 g_free(wsse_security
);
462 /* WebTicket for federated authentication */
464 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
468 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
471 success
= sipe_svc_webticket(sipe_private
,
472 ccd
->webticket_fedbearer_uri
,
479 ccd
->webticket_for_certprov
= TRUE
;
482 /* callback data passed down the line */
485 g_free(wsse_security
);
490 /* Retry with federated authentication? */
491 success
= ccd
->webticket_fedbearer_uri
&& !ccd
->tried_fedbearer
;
493 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
496 ccd
->tried_fedbearer
= TRUE
;
497 success
= sipe_svc_webticket_lmc(sipe_private
,
499 ccd
->webticket_fedbearer_uri
,
502 ccd
->webticket_for_certprov
= FALSE
;
505 /* callback data passed down the line */
512 certificate_failure(sipe_private
,
513 _("Web ticket request to %s failed"),
517 callback_data_free(ccd
);
520 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
522 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
524 gpointer callback_data
)
526 struct certificate_callback_data
*ccd
= callback_data
;
529 const sipe_xml
*node
;
531 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
534 /* Authentication ports accepted by WebTicket Service */
535 for (node
= sipe_xml_child(metadata
, "service/port");
537 node
= sipe_xml_twin(node
)) {
538 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
543 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
544 "WebTicketServiceAnon")) {
545 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Anon Auth URI %s", auth_uri
);
546 g_free(ccd
->webticket_anon_uri
);
547 ccd
->webticket_anon_uri
= g_strdup(auth_uri
);
548 } else if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
550 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri
);
551 g_free(ccd
->webticket_fedbearer_uri
);
552 ccd
->webticket_fedbearer_uri
= g_strdup(auth_uri
);
557 if (ccd
->webticket_anon_uri
|| ccd
->webticket_fedbearer_uri
) {
560 if (ccd
->webticket_anon_uri
) {
561 /* Try anonymous authentication first */
562 /* Entropy: 256 random bits */
563 sipe_svc_fill_random(&ccd
->entropy
, 256);
565 success
= sipe_svc_webticket(sipe_private
,
566 ccd
->webticket_anon_uri
,
573 ccd
->webticket_for_certprov
= TRUE
;
575 ccd
->tried_fedbearer
= TRUE
;
576 success
= sipe_svc_webticket_lmc(sipe_private
,
578 ccd
->webticket_fedbearer_uri
,
581 ccd
->webticket_for_certprov
= FALSE
;
585 /* callback data passed down the line */
588 certificate_failure(sipe_private
,
589 _("Can't request security token from %s"),
590 ccd
->webticket_anon_uri
? ccd
->webticket_anon_uri
: ccd
->webticket_fedbearer_uri
);
594 certificate_failure(sipe_private
,
595 _("Can't find the authentication port for TLS-DSK web ticket URI %s"),
600 certificate_failure(sipe_private
,
601 _("Can't retrieve metadata for TLS-DSK web ticket URI %s"),
605 callback_data_free(ccd
);
608 static void certprov_metadata(struct sipe_core_private
*sipe_private
,
610 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
612 gpointer callback_data
)
614 struct certificate_callback_data
*ccd
= callback_data
;
617 const sipe_xml
*node
;
618 gchar
*ticket_uri
= NULL
;
620 SIPE_DEBUG_INFO("certprov_metadata: metadata for service %s retrieved successfully",
623 /* WebTicket policies accepted by Certificate Provisioning Service */
624 for (node
= sipe_xml_child(metadata
, "Policy");
626 node
= sipe_xml_twin(node
)) {
627 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
628 "CertProvisioningServiceWebTicketProof_SHA1_policy")) {
630 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: WebTicket policy found");
632 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
633 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
635 SIPE_DEBUG_INFO("certprov_metadata: WebTicket URI %s", ticket_uri
);
637 certificate_failure(sipe_private
,
638 _("Can't find the WebTicket URI for TLS-DSK certificate provisioning URI %s"),
647 /* Authentication ports accepted by Certificate Provisioning Service */
648 for (node
= sipe_xml_child(metadata
, "service/port");
650 node
= sipe_xml_twin(node
)) {
651 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
652 "CertProvisioningServiceWebTicketProof_SHA1")) {
653 const gchar
*auth_uri
;
655 SIPE_DEBUG_INFO_NOFORMAT("certprov_metadata: authentication port found");
657 auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
661 SIPE_DEBUG_INFO("certprov_metadata: CertProv Auth URI %s", auth_uri
);
663 if (sipe_svc_metadata(sipe_private
,
667 /* Remember for later */
668 ccd
->certprov_uri
= g_strdup(auth_uri
);
670 /* callback data passed down the line */
673 certificate_failure(sipe_private
,
674 _("Can't request metadata from %s"),
685 certificate_failure(sipe_private
,
686 _("Can't find the authentication port for TLS-DSK certificate provisioning URI %s"),
691 certificate_failure(sipe_private
,
692 _("Can't find the WebTicket Policy for TLS-DSK certificate provisioning URI %s"),
697 certificate_failure(sipe_private
,
698 _("Can't retrieve metadata for TLS-DSK certificate provisioning URI %s"),
702 callback_data_free(ccd
);
705 gboolean
sipe_certificate_tls_dsk_generate(struct sipe_core_private
*sipe_private
,
707 const gchar
*authuser
,
710 struct certificate_callback_data
*ccd
= g_new0(struct certificate_callback_data
, 1);
713 ccd
->target
= g_strdup(target
);
714 ccd
->authuser
= g_strdup(authuser
);
716 ret
= sipe_svc_metadata(sipe_private
, uri
, certprov_metadata
, ccd
);
718 callback_data_free(ccd
);