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"
28 #include "ciphers/hmaccipher.h"
29 #include "ciphers/sha1hash.h"
32 static const JabberScramHash hashes
[] = {
33 { "-SHA-1", purple_sha1_hash_new
, 20 },
36 static const JabberScramHash
*mech_to_hash(const char *mech
)
40 g_return_val_if_fail(mech
!= NULL
&& *mech
!= '\0', NULL
);
42 for (i
= 0; i
< G_N_ELEMENTS(hashes
); ++i
) {
43 if (strstr(mech
, hashes
[i
].mech_substr
))
47 purple_debug_error("jabber", "Unknown SCRAM mechanism %s\n", mech
);
48 g_return_val_if_reached(NULL
);
52 /* XXX: this code is not (yet) used */
58 N_("Invalid Encoding")},
59 { "extensions-not-supported",
60 N_("Unsupported Extension") },
61 { "channel-bindings-dont-match",
62 N_("Unexpected response from the server. This may indicate a possible MITM attack") },
63 { "server-does-support-channel-binding",
64 N_("The server does support channel binding, but did not appear to advertise it. This indicates a likely MITM attack") },
65 { "channel-binding-not-supported",
66 N_("Server does not support channel binding") },
67 { "unsupported-channel-binding-type",
68 N_("Unsupported channel binding method") },
70 N_("User not found") },
71 { "invalid-username-encoding",
72 N_("Invalid Username Encoding") },
74 N_("Resource Constraint") },
80 guchar
*jabber_scram_hi(const JabberScramHash
*hash
, const GString
*str
,
81 GString
*salt
, guint iterations
)
89 g_return_val_if_fail(hash
!= NULL
, NULL
);
90 g_return_val_if_fail(str
!= NULL
&& str
->len
> 0, NULL
);
91 g_return_val_if_fail(salt
!= NULL
&& salt
->len
> 0, NULL
);
92 g_return_val_if_fail(iterations
> 0, NULL
);
94 prev
= g_new0(guint8
, hash
->size
);
95 tmp
= g_new0(guint8
, hash
->size
);
96 result
= g_new0(guint8
, hash
->size
);
98 hasher
= hash
->new_cipher();
99 cipher
= purple_hmac_cipher_new(hasher
);
100 g_object_unref(G_OBJECT(hasher
));
102 /* Append INT(1), a four-octet encoding of the integer 1, most significant
104 g_string_append_len(salt
, "\0\0\0\1", 4);
107 purple_cipher_set_key(cipher
, (guchar
*)str
->str
, str
->len
);
108 purple_cipher_append(cipher
, (guchar
*)salt
->str
, salt
->len
);
109 purple_cipher_digest(cipher
, result
, hash
->size
);
111 memcpy(prev
, result
, hash
->size
);
113 /* Compute U1...Ui */
114 for (i
= 1; i
< iterations
; ++i
) {
116 purple_cipher_reset(cipher
);
117 purple_cipher_set_key(cipher
, (guchar
*)str
->str
, str
->len
);
118 purple_cipher_append(cipher
, prev
, hash
->size
);
119 purple_cipher_digest(cipher
, tmp
, hash
->size
);
121 for (j
= 0; j
< hash
->size
; ++j
)
124 memcpy(prev
, tmp
, hash
->size
);
127 g_object_unref(G_OBJECT(cipher
));
134 * Helper functions for doing the SCRAM calculations. The first argument
135 * is the hash algorithm. All buffers must be of the appropriate size
136 * according to the JabberScramHash.
138 * "str" is a NULL-terminated string for hmac().
140 * Needless to say, these are fragile.
143 hmac(const JabberScramHash
*hash
, guchar
*out
, const guchar
*key
, const gchar
*str
)
146 PurpleCipher
*cipher
;
148 hasher
= hash
->new_cipher();
149 cipher
= purple_hmac_cipher_new(hasher
);
150 g_object_unref(G_OBJECT(hasher
));
151 purple_cipher_set_key(cipher
, key
, hash
->size
);
152 purple_cipher_append(cipher
, (guchar
*)str
, strlen(str
));
153 purple_cipher_digest(cipher
, out
, hash
->size
);
154 g_object_unref(G_OBJECT(cipher
));
158 hash(const JabberScramHash
*hash
, guchar
*out
, const guchar
*data
)
162 hasher
= hash
->new_cipher();
163 purple_hash_append(hasher
, data
, hash
->size
);
164 purple_hash_digest(hasher
, out
, hash
->size
);
165 g_object_unref(G_OBJECT(hasher
));
169 jabber_scram_calc_proofs(JabberScramData
*data
, GString
*salt
, guint iterations
)
171 guint hash_len
= data
->hash
->size
;
174 GString
*pass
= g_string_new(data
->password
);
176 guchar
*salted_password
;
177 guchar
*client_key
, *stored_key
, *client_signature
, *server_key
;
179 data
->client_proof
= g_string_sized_new(hash_len
);
180 data
->client_proof
->len
= hash_len
;
181 data
->server_signature
= g_string_sized_new(hash_len
);
182 data
->server_signature
->len
= hash_len
;
184 salted_password
= jabber_scram_hi(data
->hash
, pass
, salt
, iterations
);
186 memset(pass
->str
, 0, pass
->allocated_len
);
187 g_string_free(pass
, TRUE
);
189 if (!salted_password
)
192 client_key
= g_new0(guchar
, hash_len
);
193 stored_key
= g_new0(guchar
, hash_len
);
194 client_signature
= g_new0(guchar
, hash_len
);
195 server_key
= g_new0(guchar
, hash_len
);
197 /* client_key = HMAC(salted_password, "Client Key") */
198 hmac(data
->hash
, client_key
, salted_password
, "Client Key");
199 /* server_key = HMAC(salted_password, "Server Key") */
200 hmac(data
->hash
, server_key
, salted_password
, "Server Key");
201 g_free(salted_password
);
203 /* stored_key = HASH(client_key) */
204 hash(data
->hash
, stored_key
, client_key
);
206 /* client_signature = HMAC(stored_key, auth_message) */
207 hmac(data
->hash
, client_signature
, stored_key
, data
->auth_message
->str
);
208 /* server_signature = HMAC(server_key, auth_message) */
209 hmac(data
->hash
, (guchar
*)data
->server_signature
->str
, server_key
, data
->auth_message
->str
);
211 /* client_proof = client_key XOR client_signature */
212 for (i
= 0; i
< hash_len
; ++i
)
213 data
->client_proof
->str
[i
] = client_key
[i
] ^ client_signature
[i
];
216 g_free(client_signature
);
224 parse_server_step1(JabberScramData
*data
, const char *challenge
,
225 gchar
**out_nonce
, GString
**out_salt
, guint
*out_iterations
)
228 char *token
, *decoded
, *tmp
;
231 GString
*salt
= NULL
;
234 tokens
= g_strsplit(challenge
, ",", -1);
239 if (token
[0] != 'r' || token
[1] != '=')
242 /* Ensure that the first cnonce_len bytes of the nonce are the original
243 * cnonce we sent to the server.
245 if (0 != strncmp(data
->cnonce
, token
+ 2, strlen(data
->cnonce
)))
248 nonce
= g_strdup(token
+ 2);
250 /* The Salt, base64-encoded */
252 if (token
[0] != 's' || token
[1] != '=')
255 decoded
= (gchar
*)purple_base64_decode(token
+ 2, &len
);
256 if (!decoded
|| *decoded
== '\0') {
260 salt
= g_string_new_len(decoded
, len
);
263 /* The iteration count */
265 if (token
[0] != 'i' || token
[1] != '=' || token
[2] == '\0')
268 /* Validate the string */
269 for (tmp
= token
+ 2; *tmp
; ++tmp
)
270 if (!g_ascii_isdigit(*tmp
))
273 iterations
= strtoul(token
+ 2, NULL
, 10);
278 *out_iterations
= iterations
;
284 g_string_free(salt
, TRUE
);
290 parse_server_step2(JabberScramData
*data
, const char *challenge
, gchar
**out_verifier
)
295 tokens
= g_strsplit(challenge
, ",", -1);
300 if (token
[0] != 'v' || token
[1] != '=' || token
[2] == '\0') {
305 *out_verifier
= g_strdup(token
+ 2);
311 jabber_scram_feed_parser(JabberScramData
*data
, gchar
*in
, gchar
**out
)
315 g_return_val_if_fail(data
!= NULL
, FALSE
);
317 g_string_append_c(data
->auth_message
, ',');
318 g_string_append(data
->auth_message
, in
);
320 if (data
->step
== 1) {
321 gchar
*nonce
, *proof
;
325 ret
= parse_server_step1(data
, in
, &nonce
, &salt
, &iterations
);
329 g_string_append_c(data
->auth_message
, ',');
331 /* "biws" is the base64 encoding of "n,,". I promise. */
332 g_string_append_printf(data
->auth_message
, "c=%s,r=%s", "biws", nonce
);
333 #ifdef CHANNEL_BINDING
337 ret
= jabber_scram_calc_proofs(data
, salt
, iterations
);
339 g_string_free(salt
, TRUE
);
346 proof
= purple_base64_encode((guchar
*)data
->client_proof
->str
, data
->client_proof
->len
);
347 *out
= g_strdup_printf("c=%s,r=%s,p=%s", "biws", nonce
, proof
);
350 } else if (data
->step
== 2) {
351 gchar
*server_sig
, *enc_server_sig
;
354 ret
= parse_server_step2(data
, in
, &enc_server_sig
);
358 server_sig
= (gchar
*)purple_base64_decode(enc_server_sig
, &len
);
359 g_free(enc_server_sig
);
361 if (server_sig
== NULL
|| len
!= data
->server_signature
->len
) {
366 if (0 != memcmp(server_sig
, data
->server_signature
->str
, len
)) {
374 purple_debug_error("jabber", "SCRAM: There is no step %d\n", data
->step
);
381 static gchar
*escape_username(const gchar
*in
)
385 tmp
= purple_strreplace(in
, "=", "=3D");
386 tmp2
= purple_strreplace(tmp
, ",", "=2C");
391 static JabberSaslState
392 scram_start(JabberStream
*js
, PurpleXmlNode
*mechanisms
, PurpleXmlNode
**out
, char **error
)
394 PurpleXmlNode
*reply
;
395 JabberScramData
*data
;
397 #ifdef CHANNEL_BINDING
398 gboolean binding_supported
= TRUE
;
400 gchar
*dec_out
, *enc_out
;
401 gchar
*prepped_node
, *tmp
;
404 prepped_node
= jabber_saslprep(js
->user
->node
);
406 *error
= g_strdup(_("Unable to canonicalize username"));
407 return JABBER_SASL_STATE_FAIL
;
410 tmp
= escape_username(prepped_node
);
411 g_free(prepped_node
);
414 prepped_pass
= jabber_saslprep(purple_connection_get_password(js
->gc
));
416 g_free(prepped_node
);
417 *error
= g_strdup(_("Unable to canonicalize password"));
418 return JABBER_SASL_STATE_FAIL
;
421 data
= js
->auth_mech_data
= g_new0(JabberScramData
, 1);
422 data
->hash
= mech_to_hash(js
->auth_mech
->name
);
423 data
->password
= prepped_pass
;
425 #ifdef CHANNEL_BINDING
426 if (strstr(js
->auth_mech_name
, "-PLUS"))
427 data
->channel_binding
= TRUE
;
429 cnonce
= ((guint64
)g_random_int() << 32) | g_random_int();
430 data
->cnonce
= purple_base64_encode((guchar
*)&cnonce
, sizeof(cnonce
));
432 data
->auth_message
= g_string_new(NULL
);
433 g_string_printf(data
->auth_message
, "n=%s,r=%s",
434 prepped_node
, data
->cnonce
);
435 g_free(prepped_node
);
439 reply
= purple_xmlnode_new("auth");
440 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
441 purple_xmlnode_set_attrib(reply
, "mechanism", js
->auth_mech
->name
);
443 /* TODO: Channel binding */
444 dec_out
= g_strdup_printf("%c,,%s", 'n', data
->auth_message
->str
);
445 enc_out
= purple_base64_encode((guchar
*)dec_out
, strlen(dec_out
));
446 purple_debug_misc("jabber", "initial SCRAM message '%s'\n", dec_out
);
448 purple_xmlnode_insert_data(reply
, enc_out
, -1);
454 return JABBER_SASL_STATE_CONTINUE
;
457 static JabberSaslState
458 scram_handle_challenge(JabberStream
*js
, PurpleXmlNode
*challenge
, PurpleXmlNode
**out
, char **error
)
460 JabberScramData
*data
= js
->auth_mech_data
;
461 PurpleXmlNode
*reply
;
462 gchar
*enc_in
, *dec_in
= NULL
;
463 gchar
*enc_out
= NULL
, *dec_out
= NULL
;
465 JabberSaslState state
= JABBER_SASL_STATE_FAIL
;
467 enc_in
= purple_xmlnode_get_data(challenge
);
468 if (!enc_in
|| *enc_in
== '\0') {
469 reply
= purple_xmlnode_new("abort");
470 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
472 *error
= g_strdup(_("Invalid challenge from server"));
476 dec_in
= (gchar
*)purple_base64_decode(enc_in
, &len
);
477 if (!dec_in
|| len
!= strlen(dec_in
)) {
478 /* Danger afoot; SCRAM shouldn't contain NUL bytes */
479 reply
= purple_xmlnode_new("abort");
480 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
482 *error
= g_strdup(_("Malicious challenge from server"));
486 purple_debug_misc("jabber", "decoded challenge: %s\n", dec_in
);
488 if (!jabber_scram_feed_parser(data
, dec_in
, &dec_out
)) {
489 reply
= purple_xmlnode_new("abort");
490 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
492 *error
= g_strdup(_("Invalid challenge from server"));
498 reply
= purple_xmlnode_new("response");
499 purple_xmlnode_set_namespace(reply
, NS_XMPP_SASL
);
501 purple_debug_misc("jabber", "decoded response: %s\n", dec_out
? dec_out
: "(null)");
503 enc_out
= purple_base64_encode((guchar
*)dec_out
, strlen(dec_out
));
504 purple_xmlnode_insert_data(reply
, enc_out
, -1);
507 state
= JABBER_SASL_STATE_CONTINUE
;
519 static JabberSaslState
520 scram_handle_success(JabberStream
*js
, PurpleXmlNode
*packet
, char **error
)
522 JabberScramData
*data
= js
->auth_mech_data
;
523 char *enc_in
, *dec_in
;
524 char *dec_out
= NULL
;
527 enc_in
= purple_xmlnode_get_data(packet
);
528 if (data
->step
!= 3 && (!enc_in
|| *enc_in
== '\0')) {
529 *error
= g_strdup(_("Invalid challenge from server"));
531 return JABBER_SASL_STATE_FAIL
;
534 if (data
->step
== 3) {
536 * If the server took the slow approach (sending the verifier
537 * as a challenge/response pair), we get here.
540 return JABBER_SASL_STATE_OK
;
543 if (data
->step
!= 2) {
544 *error
= g_strdup(_("Unexpected response from server"));
546 return JABBER_SASL_STATE_FAIL
;
549 dec_in
= (gchar
*)purple_base64_decode(enc_in
, &len
);
551 if (!dec_in
|| len
!= strlen(dec_in
)) {
552 /* Danger afoot; SCRAM shouldn't contain NUL bytes */
554 *error
= g_strdup(_("Malicious challenge from server"));
555 return JABBER_SASL_STATE_FAIL
;
558 purple_debug_misc("jabber", "decoded success: %s\n", dec_in
);
560 if (!jabber_scram_feed_parser(data
, dec_in
, &dec_out
) || dec_out
!= NULL
) {
563 *error
= g_strdup(_("Invalid challenge from server"));
564 return JABBER_SASL_STATE_FAIL
;
569 return JABBER_SASL_STATE_OK
;
572 void jabber_scram_data_destroy(JabberScramData
*data
)
574 g_free(data
->cnonce
);
575 if (data
->auth_message
)
576 g_string_free(data
->auth_message
, TRUE
);
577 if (data
->client_proof
)
578 g_string_free(data
->client_proof
, TRUE
);
579 if (data
->server_signature
)
580 g_string_free(data
->server_signature
, TRUE
);
581 if (data
->password
) {
582 memset(data
->password
, 0, strlen(data
->password
));
583 g_free(data
->password
);
589 static void scram_dispose(JabberStream
*js
)
591 if (js
->auth_mech_data
) {
592 jabber_scram_data_destroy(js
->auth_mech_data
);
593 js
->auth_mech_data
= NULL
;
597 static JabberSaslMech scram_sha1_mech
= {
599 "SCRAM-SHA-1", /* name */
601 scram_handle_challenge
,
602 scram_handle_success
,
603 NULL
, /* handle_failure */
607 #ifdef CHANNEL_BINDING
608 /* With channel binding */
609 static JabberSaslMech scram_sha1_plus_mech
= {
610 scram_sha1_mech
.priority
+ 1, /* priority */
611 "SCRAM-SHA-1-PLUS", /* name */
613 scram_handle_challenge
,
614 scram_handle_success
,
615 NULL
, /* handle_failure */
620 JabberSaslMech
**jabber_auth_get_scram_mechs(gint
*count
)
622 static JabberSaslMech
*mechs
[] = {
624 #ifdef CHANNEL_BINDING
625 &scram_sha1_plus_mech
,
629 *count
= G_N_ELEMENTS(mechs
);