From 637fdea366a8a87024322d283eba89067a403493 Mon Sep 17 00:00:00 2001 Message-Id: <637fdea366a8a87024322d283eba89067a403493.1713579322.git.mdw@distorted.org.uk> From: Mark Wooding Date: Tue, 2 Oct 2007 11:23:41 +0100 Subject: [PATCH] support alternative hashes for authentication Organization: Straylight/Edgeware From: Richard Kettlewell --- doc/disorder_config.5.in | 6 ++++ doc/disorder_protocol.5.in | 6 ++-- lib/authhash.c | 59 +++++++++++++++++++++++++++++++------- lib/authhash.h | 3 +- lib/client.c | 17 ++++++++--- lib/configuration.c | 17 +++++++++++ lib/configuration.h | 3 ++ lib/eclient.c | 21 ++++++++++++-- server/Makefile.am | 11 +++---- server/server.c | 13 +++++++-- 10 files changed, 128 insertions(+), 28 deletions(-) diff --git a/doc/disorder_config.5.in b/doc/disorder_config.5.in index 4cde04f..b1ea0b1 100644 --- a/doc/disorder_config.5.in +++ b/doc/disorder_config.5.in @@ -142,6 +142,12 @@ automatically included, but should include the proper extension. .IP The default is \fB{/artist}{/album}{/title}{ext}\fR. .TP +.B authorization_algorthm \fIALGORITHM\fR +Defines the algorithm used to authenticate clients. The valid options +are sha1 (the default), sha256, sha384 and sha512. See +.BR disorder_protocol (5) +for more details. +.TP .B broadcast \fIADDRESS\fR \fIPORT\fR Transmit sound data to \fIADDRESS\fR using UDP port \fIPORT\fR. This implies \fBspeaker_backend network\fR. diff --git a/doc/disorder_protocol.5.in b/doc/disorder_protocol.5.in index 07fada9..9e64d94 100644 --- a/doc/disorder_protocol.5.in +++ b/doc/disorder_protocol.5.in @@ -231,10 +231,10 @@ Unset a global preference. Authenticate as \fIUSER\fR. .IP When a connection is made the server sends a \fB221\fR response before any -command is received. As its first field this contains a challenge string -encoded in hex. +command is received. This contains an algorithm name and a challenge encoded +in hex. Currently the algorithm name is omitted if it is "sha1". .IP -The \fIRESPONSE\fR consists of the SHA-1 hash of the user's password +The \fIRESPONSE\fR consists of the selected hash of the user's password concatenated with the challenge, encoded in hex. .TP .B version diff --git a/lib/authhash.c b/lib/authhash.c index b97204d..1eaf27f 100644 --- a/lib/authhash.c +++ b/lib/authhash.c @@ -30,46 +30,85 @@ #include "log.h" #include "authhash.h" -#ifndef AUTHHASH -/** @brief Which hash function to use */ -# define AUTHHASH GCRY_MD_SHA1 -#endif +/** @brief Structure of algorithm lookup table */ +struct algorithm { + const char *name; + int id; +}; + +/** @brief Algorithm lookup table + * + * We don't use gcry_md_map_name() since that would import gcrypt's API into + * the disorder protocol. + */ +static const struct algorithm algorithms[] = { + { "SHA1", GCRY_MD_SHA1 }, + { "sha1", GCRY_MD_SHA1 }, + { "SHA256", GCRY_MD_SHA256 }, + { "sha256", GCRY_MD_SHA256 }, + { "SHA384", GCRY_MD_SHA384 }, + { "sha384", GCRY_MD_SHA384 }, + { "SHA512", GCRY_MD_SHA512 }, + { "sha512", GCRY_MD_SHA512 }, +}; + +/** @brief Number of supported algorithms */ +#define NALGORITHMS (sizeof algorithms / sizeof *algorithms) /** @brief Perform the authorization hash function * @param challenge Pointer to challange * @param nchallenge Size of challenge * @param password Password + * @param algo Algorithm to use * * Computes H(challenge|password) and returns it as a newly allocated hex - * string. Currently the hash function is SHA-1, but this may be changed in - * future versions; see @ref AUTHHASH. + * string, or returns NULL on error. */ const char *authhash(const void *challenge, size_t nchallenge, - const char *password) { + const char *password, const char *algo) { gcrypt_hash_handle h; const char *res; + size_t n; + int id; assert(challenge != 0); assert(password != 0); + assert(algo != 0); + for(n = 0; n < NALGORITHMS; ++n) + if(!strcmp(algo, algorithms[n].name)) + break; + if(n >= NALGORITHMS) + return NULL; + id = algorithms[n].id; #if HAVE_GCRY_ERROR_T { gcry_error_t e; - if((e = gcry_md_open(&h, AUTHHASH, 0))) { + if((e = gcry_md_open(&h, id, 0))) { error(0, "gcry_md_open: %s", gcry_strerror(e)); return 0; } } #else - h = gcry_md_open(AUTHHASH, 0); + h = gcry_md_open(id, 0); #endif gcry_md_write(h, password, strlen(password)); gcry_md_write(h, challenge, nchallenge); - res = hex(gcry_md_read(h, AUTHHASH), gcry_md_get_algo_dlen(AUTHHASH)); + res = hex(gcry_md_read(h, id), gcry_md_get_algo_dlen(id)); gcry_md_close(h); return res; } +/** @brief Return non-zero if @p algo is a valid algorithm */ +int valid_authhash(const char *algo) { + size_t n; + + for(n = 0; n < NALGORITHMS; ++n) + if(!strcmp(algo, algorithms[n].name)) + return 1; + return 0; +} + /* Local Variables: c-basic-offset:2 diff --git a/lib/authhash.h b/lib/authhash.h index 9999279..38981e1 100644 --- a/lib/authhash.h +++ b/lib/authhash.h @@ -21,7 +21,8 @@ #define AUTHHASH_H const char *authhash(const void *challenge, size_t nchallenge, - const char *user); + const char *user, const char *algo); +int valid_authhash(const char *algo); #endif /* AUTHHASH_H */ diff --git a/lib/client.c b/lib/client.c index 97dde93..979e54a 100644 --- a/lib/client.c +++ b/lib/client.c @@ -207,11 +207,12 @@ int disorder_connect_sock(disorder_client *c, const char *username, const char *password, const char *ident) { - int fd = -1, fd2 = -1; + int fd = -1, fd2 = -1, nrvec; unsigned char *nonce; size_t nl; const char *res; - char *r; + char *r, **rvec; + const char *algo = "SHA1"; if(!password) { error(0, "no password found"); @@ -243,9 +244,17 @@ int disorder_connect_sock(disorder_client *c, c->ident = xstrdup(ident); if(disorder_simple(c, &r, 0, (const char *)0)) return -1; - if(!(nonce = unhex(r, &nl))) + if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0))) + return -1; + if(nrvec > 1) { + algo = *rvec++; + --nrvec; + } + if(!nrvec) + return -1; + if(!(nonce = unhex(*rvec, &nl))) return -1; - if(!(res = authhash(nonce, nl, password))) goto error; + if(!(res = authhash(nonce, nl, password, algo))) goto error; if(disorder_simple(c, 0, "user", username, res, (char *)0)) return -1; c->user = xstrdup(username); diff --git a/lib/configuration.c b/lib/configuration.c index ae71d63..ad41024 100644 --- a/lib/configuration.c +++ b/lib/configuration.c @@ -51,6 +51,7 @@ #include "printf.h" #include "regsub.h" #include "signame.h" +#include "authhash.h" /** @brief Path to config file * @@ -812,6 +813,20 @@ static int validate_port(const struct config_state attribute((unused)) *cs, } } +static int validate_algo(const struct config_state attribute((unused)) *cs, + int nvec, + char **vec) { + if(nvec != 1) { + error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line); + return -1; + } + if(!valid_authhash(vec[0])) { + error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]); + return -1; + } + return 0; +} + /** @brief Item name and and offset */ #define C(x) #x, offsetof(struct config, x) /** @brief Item name and and offset */ @@ -821,6 +836,7 @@ static int validate_port(const struct config_state attribute((unused)) *cs, static const struct conf conf[] = { { C(alias), &type_string, validate_alias }, { C(allow), &type_stringlist_accum, validate_allow }, + { C(authorization_algorithm), &type_string, validate_algo }, { C(broadcast), &type_stringlist, validate_addrport }, { C(broadcast_from), &type_stringlist, validate_addrport }, { C(channel), &type_string, validate_channel }, @@ -975,6 +991,7 @@ static struct config *config_default(void) { c->queue_pad = 10; c->speaker_backend = -1; c->multicast_ttl = 1; + c->authorization_algorithm = xstrdup("sha1"); return c; } diff --git a/lib/configuration.h b/lib/configuration.h index 84b1eb8..e9893f0 100644 --- a/lib/configuration.h +++ b/lib/configuration.h @@ -96,6 +96,9 @@ struct transformlist { struct config { /* server config */ + /** @brief Authorization algorithm */ + char *authorization_algorithm; + /** @brief All players */ struct stringlistlist player; diff --git a/lib/eclient.c b/lib/eclient.c index e5b723d..7661679 100644 --- a/lib/eclient.c +++ b/lib/eclient.c @@ -503,16 +503,31 @@ static void authbanner_opcallback(disorder_eclient *c, size_t nonce_len; const unsigned char *nonce; const char *res; + char **rvec; + int nrvec; + const char *algo = "SHA1"; D(("authbanner_opcallback")); - if(c->rc / 100 != 2) { - /* Banner told us to go away. We cannot proceed. */ + if(c->rc / 100 != 2 + || !(rvec = split(c->line + 4, &nrvec, SPLIT_QUOTES, 0, 0)) + || nrvec < 1) { + /* Banner told us to go away, or was malformed. We cannot proceed. */ protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); disorder_eclient_close(c); return; } + if(nrvec > 1) { + algo = *rvec++; + --nrvec; + } nonce = unhex(c->line + 4, &nonce_len); - res = authhash(nonce, nonce_len, config->password); + res = authhash(nonce, nonce_len, config->password, algo); + if(!res) { + protocol_error(c, op, c->rc, "%s: unknown authentication algorithm '%s'", + c->ident, algo); + disorder_eclient_close(c); + return; + } stash_command(c, 1/*queuejump*/, authuser_opcallback, 0/*completed*/, 0/*v*/, "user", quoteutf8(config->username), quoteutf8(res), (char *)0); diff --git a/server/Makefile.am b/server/Makefile.am index e63f6aa..2b65595 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -41,7 +41,7 @@ disorderd_DEPENDENCIES=../lib/libdisorder.a disorder_deadlock_SOURCES=deadlock.c \ trackdb.c trackdb.h disorder_deadlock_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ - $(LIBDB) $(LIBPCRE) $(LIBICONV) + $(LIBDB) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) disorder_deadlock_DEPENDENCIES=../lib/libdisorder.a disorder_speaker_SOURCES=speaker.c speaker.h \ @@ -58,7 +58,8 @@ disorder_decode_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ disorder_decode_DEPENDENCIES=../lib/libdisorder.a disorder_normalize_SOURCES=normalize.c -disorder_normalize_LDADD=$(LIBOBJS) ../lib/libdisorder.a $(LIBPCRE) $(LIBICONV) +disorder_normalize_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ + $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) disorder_normalize_DEPENDENCIES=../lib/libdisorder.a disorder_rescan_SOURCES=rescan.c \ @@ -66,7 +67,7 @@ disorder_rescan_SOURCES=rescan.c \ trackdb.c trackdb.h exports.c \ ../lib/memgc.c disorder_rescan_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ - $(LIBDB) $(LIBGC) $(LIBPCRE) $(LIBICONV) + $(LIBDB) $(LIBGC) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) disorder_rescan_LDFLAGS=-export-dynamic disorder_rescan_DEPENDENCIES=../lib/libdisorder.a @@ -74,7 +75,7 @@ disorder_dump_SOURCES=dump.c \ trackdb.c trackdb.h \ ../lib/memgc.c disorder_dump_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ - $(LIBPCRE) $(LIBDB) $(LIBICONV) $(LIBGC) + $(LIBPCRE) $(LIBDB) $(LIBICONV) $(LIBGC) $(LIBGCRYPT) disorder_dump_DEPENDENCIES=$(LIBOBJS) ../lib/libdisorder.a disorder_cgi_SOURCES=dcgi.c dcgi.h \ @@ -86,7 +87,7 @@ disorder_cgi_LDFLAGS=-export-dynamic disorder_cgi_DEPENDENCIES=../lib/libdisorder.a trackname_SOURCES=trackname.c -trackname_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBICONV) +trackname_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) trackname_DEPENDENCIES=../lib/libdisorder.a install-exec-hook: diff --git a/server/server.c b/server/server.c index a30e978..6ee11c9 100644 --- a/server/server.c +++ b/server/server.c @@ -391,7 +391,8 @@ static int c_user(struct conn *c, sink_writes(ev_writer_sink(c->w), "530 authentication failed\n"); return 1; } - res = authhash(c->nonce, sizeof c->nonce, config->allow.s[n].s[1]); + res = authhash(c->nonce, sizeof c->nonce, config->allow.s[n].s[1], + config->authorization_algorithm); if(wideopen || (res && !strcmp(res, vec[1]))) { c->who = vec[0]; /* currently we only bother logging remote connections */ @@ -1084,7 +1085,15 @@ static int listen_callback(ev_source *ev, c->reader = reader_callback; c->l = l; gcry_randomize(c->nonce, sizeof c->nonce, GCRY_STRONG_RANDOM); - sink_printf(ev_writer_sink(c->w), "231 %s\n", hex(c->nonce, sizeof c->nonce)); + if(!strcmp(config->authorization_algorithm, "sha1") + || !strcmp(config->authorization_algorithm, "SHA1")) { + sink_printf(ev_writer_sink(c->w), "231 %s\n", + hex(c->nonce, sizeof c->nonce)); + } else { + sink_printf(ev_writer_sink(c->w), "231 %s %s\n", + config->authorization_algorithm, + hex(c->nonce, sizeof c->nonce)); + } return 0; } -- [mdw]