2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "auth_scram.h"
30 static const JabberScramHash hashes
[] = {
31 { "-SHA-1", G_CHECKSUM_SHA1
},
34 static const JabberScramHash
*mech_to_hash(const char *mech
)
38 g_return_val_if_fail(mech
!= NULL
&& *mech
!= '\0', NULL
);
40 for (i
= 0; i
< G_N_ELEMENTS(hashes
); ++i
) {
41 if (strstr(mech
, hashes
[i
].mech_substr
))
45 purple_debug_error("jabber", "Unknown SCRAM mechanism %s\n", mech
);
46 g_return_val_if_reached(NULL
);
49 guchar
*jabber_scram_hi(const JabberScramHash
*hash
, const GString
*str
,
50 GString
*salt
, guint iterations
)
58 g_return_val_if_fail(hash
!= NULL
, NULL
);
59 g_return_val_if_fail(str
!= NULL
&& str
->len
> 0, NULL
);
60 g_return_val_if_fail(salt
!= NULL
&& salt
->len
> 0, NULL
);
61 g_return_val_if_fail(iterations
> 0, NULL
);
63 digest_len
= g_checksum_type_get_length(hash
->type
);
64 prev
= g_new0(guchar
, digest_len
);
65 tmp
= g_new0(guchar
, digest_len
);
66 result
= g_new0(guchar
, digest_len
);
68 hmac
= g_hmac_new(hash
->type
, (guchar
*)str
->str
, str
->len
);
70 /* Append INT(1), a four-octet encoding of the integer 1, most significant
72 g_string_append_len(salt
, "\0\0\0\1", 4);
75 g_hmac_update(hmac
, (guchar
*)salt
->str
, salt
->len
);
76 g_hmac_get_digest(hmac
, result
, &digest_len
);
79 memcpy(prev
, result
, digest_len
);
82 for (i
= 1; i
< iterations
; ++i
) {
84 hmac
= g_hmac_new(hash
->type
, (guchar
*)str
->str
, str
->len
);
85 g_hmac_update(hmac
, prev
, digest_len
);
86 g_hmac_get_digest(hmac
, tmp
, &digest_len
);
89 for (j
= 0; j
< digest_len
; ++j
)
92 memcpy(prev
, tmp
, digest_len
);
101 * Helper functions for doing the SCRAM calculations. The first argument
102 * is the hash algorithm. All buffers must be of the appropriate size
103 * according to the JabberScramHash.
105 * "str" is a NULL-terminated string for jabber_scram_hmac().
107 * Needless to say, these are fragile.
110 jabber_scram_hmac(const JabberScramHash
*hash
, guchar
*out
, const guchar
*key
, const gchar
*str
)
113 gsize digest_len
= g_checksum_type_get_length(hash
->type
);
115 hmac
= g_hmac_new(hash
->type
, key
, digest_len
);
116 g_hmac_update(hmac
, (guchar
*)str
, -1);
117 g_hmac_get_digest(hmac
, out
, &digest_len
);
122 jabber_scram_hash(const JabberScramHash
*hash
, guchar
*out
, const guchar
*data
)
125 gsize digest_len
= g_checksum_type_get_length(hash
->type
);
127 checksum
= g_checksum_new(hash
->type
);
128 g_checksum_update(checksum
, data
, digest_len
);
129 g_checksum_get_digest(checksum
, out
, &digest_len
);
130 g_checksum_free(checksum
);
134 jabber_scram_calc_proofs(JabberScramData
*data
, GString
*salt
, guint iterations
)
136 guint hash_len
= g_checksum_type_get_length(data
->hash
->type
);
139 GString
*pass
= g_string_new(data
->password
);
141 guchar
*salted_password
;
142 guchar
*client_key
, *stored_key
, *client_signature
, *server_key
;
144 data
->client_proof
= g_string_sized_new(hash_len
);
145 data
->client_proof
->len
= hash_len
;
146 data
->server_signature
= g_string_sized_new(hash_len
);
147 data
->server_signature
->len
= hash_len
;
149 salted_password
= jabber_scram_hi(data
->hash
, pass
, salt
, iterations
);
151 memset(pass
->str
, 0, pass
->allocated_len
);
152 g_string_free(pass
, TRUE
);
154 if (!salted_password
)
157 client_key
= g_new0(guchar
, hash_len
);
158 stored_key
= g_new0(guchar
, hash_len
);
159 client_signature
= g_new0(guchar
, hash_len
);
160 server_key
= g_new0(guchar
, hash_len
);
162 /* client_key = HMAC(salted_password, "Client Key") */
163 jabber_scram_hmac(data
->hash
, client_key
, salted_password
, "Client Key");
164 /* server_key = HMAC(salted_password, "Server Key") */
165 jabber_scram_hmac(data
->hash
, server_key
, salted_password
, "Server Key");
166 g_free(salted_password
);
168 /* stored_key = HASH(client_key) */
169 jabber_scram_hash(data
->hash
, stored_key
, client_key
);
171 /* client_signature = HMAC(stored_key, auth_message) */
172 jabber_scram_hmac(data
->hash
, client_signature
, stored_key
, data
->auth_message
->str
);
173 /* server_signature = HMAC(server_key, auth_message) */
174 jabber_scram_hmac(data
->hash
, (guchar
*)data
->server_signature
->str
, server_key
, data
->auth_message
->str
);
176 /* client_proof = client_key XOR client_signature */
177 for (i
= 0; i
< hash_len
; ++i
)
178 data
->client_proof
->str
[i
] = client_key
[i
] ^ client_signature
[i
];
181 g_free(client_signature
);
189 parse_server_step1(JabberScramData
*data
, const char *challenge
,
190 gchar
**out_nonce
, GString
**out_salt
, guint
*out_iterations
)
193 char *token
, *decoded
, *tmp
;
196 GString
*salt
= NULL
;
199 tokens
= g_strsplit(challenge
, ",", -1);
204 if (token
[0] != 'r' || token
[1] != '=')
207 /* Ensure that the first cnonce_len bytes of the nonce are the original
208 * cnonce we sent to the server.
210 if (0 != strncmp(data
->cnonce
, token
+ 2, strlen(data
->cnonce
)))
213 nonce
= g_strdup(token
+ 2);
215 /* The Salt, base64-encoded */
217 if (token
[0] != 's' || token
[1] != '=')
220 decoded
= (gchar
*)g_base64_decode(token
+ 2, &len
);
221 if (!decoded
|| *decoded
== '\0') {
225 salt
= g_string_new_len(decoded
, len
);
228 /* The iteration count */
230 if (token
[0] != 'i' || token
[1] != '=' || token
[2] == '\0')
233 /* Validate the string */
234 for (tmp
= token
+ 2; *tmp
; ++tmp
)
235 if (!g_ascii_isdigit(*tmp
))
238 iterations
= strtoul(token
+ 2, NULL
, 10);
243 *out_iterations
= iterations
;
249 g_string_free(salt
, TRUE
);
255 parse_server_step2(JabberScramData
*data
, const char *challenge
, gchar
**out_verifier
)
260 tokens
= g_strsplit(challenge
, ",", -1);
265 if (token
[0] != 'v' || token
[1] != '=' || token
[2] == '\0') {
270 *out_verifier
= g_strdup(token
+ 2);
276 jabber_scram_feed_parser(JabberScramData
*data
, gchar
*in
, gchar
**out
)
280 g_return_val_if_fail(data
!= NULL
, FALSE
);
282 g_string_append_c(data
->auth_message
, ',');
283 g_string_append(data
->auth_message
, in
);
285 if (data
->step
== 1) {
286 gchar
*nonce
, *proof
;
290 ret
= parse_server_step1(data
, in
, &nonce
, &salt
, &iterations
);
294 g_string_append_c(data
->auth_message
, ',');
296 /* "biws" is the base64 encoding of "n,,". I promise. */
297 g_string_append_printf(data
->auth_message
, "c=%s,r=%s", "biws", nonce
);
298 #ifdef CHANNEL_BINDING
302 ret
= jabber_scram_calc_proofs(data
, salt
, iterations
);
304 g_string_free(salt
, TRUE
);
311 proof
= g_base64_encode((guchar
*)data
->client_proof
->str
, data
->client_proof
->len
);
312 *out
= g_strdup_printf("c=%s,r=%s,p=%s", "biws", nonce
, proof
);
315 } else if (data
->step
== 2) {
316 gchar
*server_sig
, *enc_server_sig
;
319 ret
= parse_server_step2(data
, in
, &enc_server_sig
);
323 server_sig
= (gchar
*)g_base64_decode(enc_server_sig
, &len
);
324 g_free(enc_server_sig
);
326 if (server_sig
== NULL
|| len
!= data
->server_signature
->len
) {
331 if (0 != memcmp(server_sig
, data
->server_signature
->str
, len
)) {
339 purple_debug_error("jabber", "SCRAM: There is no step %d\n", data
->step
);
346 static gchar
*escape_username(const gchar
*in
)
350 tmp
= purple_strreplace(in
, "=", "=3D");
351 tmp2
= purple_strreplace(tmp
, ",", "=2C");
356 static JabberSaslState
357 scram_start(JabberStream
*js
, PurpleXmlNode
*mechanisms
, PurpleXmlNode
**out
, char **error
)
359 PurpleXmlNode
*reply
;
360 JabberScramData
*data
;
362 #ifdef CHANNEL_BINDING
363 gboolean binding_supported
= TRUE
;
365 gchar
*dec_out
, *enc_out
;
366 gchar
*prepped_node
, *tmp
;
369 prepped_node
= jabber_saslprep(js
->user
->node
);
371 *error
= g_strdup(_("Unable to canonicalize username"));
372 return JABBER_SASL_STATE_FAIL
;
375 tmp
= escape_username(prepped_node
);
376 g_free(prepped_node
);
379 prepped_pass
= jabber_saslprep(purple_connection_get_password(js
->gc
));
381 g_free(prepped_node
);
382 *error
= g_strdup(_("Unable to canonicalize password"));
383 return JABBER_SASL_STATE_FAIL
;
386 data
= js
->auth_mech_data
= g_new0(JabberScramData
, 1);
387 data
->hash
= mech_to_hash(js
->auth_mech
->name
);
388 data
->password
= prepped_pass
;
390 #ifdef CHANNEL_BINDING
391 if (strstr(js
->auth_mech_name
, "-PLUS"))
392 data
->channel_binding
= TRUE
;
394 cnonce
= ((guint64
)g_random_int() << 32) | g_random_int();
395 data
->cnonce
= g_base64_encode((guchar
*)&cnonce
, sizeof(cnonce
));
397 data
->auth_message
= g_string_new(NULL
);
398 g_string_printf(data
->auth_message
, "n=%s,r=%s",
399 prepped_node
, data
->cnonce
);
400 g_free(prepped_node
);
404 reply
= purple_xmlnode_new("auth");
405 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
406 purple_xmlnode_set_attrib(reply
, "mechanism", js
->auth_mech
->name
);
408 /* TODO: Channel binding */
409 dec_out
= g_strdup_printf("%c,,%s", 'n', data
->auth_message
->str
);
410 enc_out
= g_base64_encode((guchar
*)dec_out
, strlen(dec_out
));
411 purple_debug_misc("jabber", "initial SCRAM message '%s'\n", dec_out
);
413 purple_xmlnode_insert_data(reply
, enc_out
, -1);
419 return JABBER_SASL_STATE_CONTINUE
;
422 static JabberSaslState
423 scram_handle_challenge(JabberStream
*js
, PurpleXmlNode
*challenge
, PurpleXmlNode
**out
, char **error
)
425 JabberScramData
*data
= js
->auth_mech_data
;
426 PurpleXmlNode
*reply
;
427 gchar
*enc_in
, *dec_in
= NULL
;
428 gchar
*enc_out
= NULL
, *dec_out
= NULL
;
430 JabberSaslState state
= JABBER_SASL_STATE_FAIL
;
432 enc_in
= purple_xmlnode_get_data(challenge
);
433 if (!enc_in
|| *enc_in
== '\0') {
434 reply
= purple_xmlnode_new("abort");
435 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
437 *error
= g_strdup(_("Invalid challenge from server"));
441 dec_in
= (gchar
*)g_base64_decode(enc_in
, &len
);
442 if (!dec_in
|| len
!= strlen(dec_in
)) {
443 /* Danger afoot; SCRAM shouldn't contain NUL bytes */
444 reply
= purple_xmlnode_new("abort");
445 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
447 *error
= g_strdup(_("Malicious challenge from server"));
451 purple_debug_misc("jabber", "decoded challenge: %s\n", dec_in
);
453 if (!jabber_scram_feed_parser(data
, dec_in
, &dec_out
)) {
454 reply
= purple_xmlnode_new("abort");
455 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
457 *error
= g_strdup(_("Invalid challenge from server"));
463 reply
= purple_xmlnode_new("response");
464 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
466 purple_debug_misc("jabber", "decoded response: %s\n", dec_out
? dec_out
: "(null)");
468 enc_out
= g_base64_encode((guchar
*)dec_out
, strlen(dec_out
));
469 purple_xmlnode_insert_data(reply
, enc_out
, -1);
472 state
= JABBER_SASL_STATE_CONTINUE
;
484 static JabberSaslState
485 scram_handle_success(JabberStream
*js
, PurpleXmlNode
*packet
, char **error
)
487 JabberScramData
*data
= js
->auth_mech_data
;
488 char *enc_in
, *dec_in
;
489 char *dec_out
= NULL
;
492 enc_in
= purple_xmlnode_get_data(packet
);
493 if (data
->step
!= 3 && (!enc_in
|| *enc_in
== '\0')) {
494 *error
= g_strdup(_("Invalid challenge from server"));
496 return JABBER_SASL_STATE_FAIL
;
499 if (data
->step
== 3) {
501 * If the server took the slow approach (sending the verifier
502 * as a challenge/response pair), we get here.
505 return JABBER_SASL_STATE_OK
;
508 if (data
->step
!= 2) {
509 *error
= g_strdup(_("Unexpected response from server"));
511 return JABBER_SASL_STATE_FAIL
;
514 dec_in
= (gchar
*)g_base64_decode(enc_in
, &len
);
516 if (!dec_in
|| len
!= strlen(dec_in
)) {
517 /* Danger afoot; SCRAM shouldn't contain NUL bytes */
519 *error
= g_strdup(_("Malicious challenge from server"));
520 return JABBER_SASL_STATE_FAIL
;
523 purple_debug_misc("jabber", "decoded success: %s\n", dec_in
);
525 if (!jabber_scram_feed_parser(data
, dec_in
, &dec_out
) || dec_out
!= NULL
) {
528 *error
= g_strdup(_("Invalid challenge from server"));
529 return JABBER_SASL_STATE_FAIL
;
534 return JABBER_SASL_STATE_OK
;
537 void jabber_scram_data_destroy(JabberScramData
*data
)
539 g_free(data
->cnonce
);
540 if (data
->auth_message
)
541 g_string_free(data
->auth_message
, TRUE
);
542 if (data
->client_proof
)
543 g_string_free(data
->client_proof
, TRUE
);
544 if (data
->server_signature
)
545 g_string_free(data
->server_signature
, TRUE
);
546 if (data
->password
) {
547 memset(data
->password
, 0, strlen(data
->password
));
548 g_free(data
->password
);
554 static void scram_dispose(JabberStream
*js
)
556 if (js
->auth_mech_data
) {
557 jabber_scram_data_destroy(js
->auth_mech_data
);
558 js
->auth_mech_data
= NULL
;
562 static JabberSaslMech scram_sha1_mech
= {
564 "SCRAM-SHA-1", /* name */
566 scram_handle_challenge
,
567 scram_handle_success
,
568 NULL
, /* handle_failure */
572 #ifdef CHANNEL_BINDING
573 /* With channel binding */
574 static JabberSaslMech scram_sha1_plus_mech
= {
575 scram_sha1_mech
.priority
+ 1, /* priority */
576 "SCRAM-SHA-1-PLUS", /* name */
578 scram_handle_challenge
,
579 scram_handle_success
,
580 NULL
, /* handle_failure */
585 JabberSaslMech
**jabber_auth_get_scram_mechs(gint
*count
)
587 static JabberSaslMech
*mechs
[] = {
589 #ifdef CHANNEL_BINDING
590 &scram_sha1_plus_mech
,
594 *count
= G_N_ELEMENTS(mechs
);