From f8ed8c89c9e9b1e5d23586063658e4c633b0555d Mon Sep 17 00:00:00 2001 From: Nicolas Favre-Felix Date: Wed, 20 Jul 2011 13:41:27 +0100 Subject: [PATCH] Started implementing the new WebSocket protocol --- Makefile | 2 +- sha1/sha1.c | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sha1/sha1.h | 54 ++++++++ websocket.c | 76 ++++------- 4 files changed, 454 insertions(+), 49 deletions(-) create mode 100644 sha1/sha1.c create mode 100644 sha1/sha1.h diff --git a/Makefile b/Makefile index 90ca0cfb..5dae2d43 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ JANSSON_OBJ=jansson/src/dump.o jansson/src/error.o jansson/src/hashtable.o janss FORMAT_OBJS=formats/json.o formats/raw.o formats/common.o formats/custom-type.o formats/bson.o HTTP_PARSER_OBJS=http-parser/http_parser.o DEPS=$(FORMAT_OBJS) $(HIREDIS_OBJ) $(JANSSON_OBJ) $(HTTP_PARSER_OBJS) -OBJS=webdis.o cmd.o worker.o slog.o server.o libb64/cencode.o acl.o md5/md5.o http.o client.o websocket.o pool.o conf.o $(DEPS) +OBJS=webdis.o cmd.o worker.o slog.o server.o libb64/cencode.o acl.o md5/md5.o sha1/sha1.o http.o client.o websocket.o pool.o conf.o $(DEPS) CFLAGS=-O3 -Wall -Wextra -I. -Ijansson/src -Ihttp-parser LDFLAGS=-levent -pthread diff --git a/sha1/sha1.c b/sha1/sha1.c new file mode 100644 index 00000000..d87c7f48 --- /dev/null +++ b/sha1/sha1.c @@ -0,0 +1,371 @@ +/* + * sha1.c + * + * Copyright (C) 1998, 2009 + * Paul E. Jones + * All Rights Reserved + * + ***************************************************************************** + * $Id: sha1.c 12 2009-06-22 19:34:25Z paulej $ + ***************************************************************************** + * + * Description: + * This file implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * The Secure Hashing Standard, which uses the Secure Hashing + * Algorithm (SHA), produces a 160-bit message digest for a + * given data stream. In theory, it is highly improbable that + * two messages will produce the same message digest. Therefore, + * this algorithm can serve as a means of providing a "fingerprint" + * for a message. + * + * Portability Issues: + * SHA-1 is defined in terms of 32-bit "words". This code was + * written with the expectation that the processor has at least + * a 32-bit machine word size. If the machine word size is larger, + * the code should still function properly. One caveat to that + * is that the input functions taking characters and character + * arrays assume that only 8 bits of information are stored in each + * character. + * + * Caveats: + * SHA-1 is designed to work with messages less than 2^64 bits + * long. Although SHA-1 allows a message digest to be generated for + * messages of any number of bits less than 2^64, this + * implementation only works with messages with a length that is a + * multiple of the size of an 8-bit character. + * + */ + +#include "sha1.h" + +/* + * Define the circular shift macro + */ +#define SHA1CircularShift(bits,word) \ + ((((word) << (bits)) & 0xFFFFFFFF) | \ + ((word) >> (32-(bits)))) + +/* Function prototypes */ +void SHA1ProcessMessageBlock(SHA1Context *); +void SHA1PadMessage(SHA1Context *); + +/* + * SHA1Reset + * + * Description: + * This function will initialize the SHA1Context in preparation + * for computing a new message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1Reset(SHA1Context *context) +{ + context->Length_Low = 0; + context->Length_High = 0; + context->Message_Block_Index = 0; + + context->Message_Digest[0] = 0x67452301; + context->Message_Digest[1] = 0xEFCDAB89; + context->Message_Digest[2] = 0x98BADCFE; + context->Message_Digest[3] = 0x10325476; + context->Message_Digest[4] = 0xC3D2E1F0; + + context->Computed = 0; + context->Corrupted = 0; +} + +/* + * SHA1Result + * + * Description: + * This function will return the 160-bit message digest into the + * Message_Digest array within the SHA1Context provided + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA-1 hash. + * + * Returns: + * 1 if successful, 0 if it failed. + * + * Comments: + * + */ +int SHA1Result(SHA1Context *context) +{ + + if (context->Corrupted) + { + return 0; + } + + if (!context->Computed) + { + SHA1PadMessage(context); + context->Computed = 1; + } + + return 1; +} + +/* + * SHA1Input + * + * Description: + * This function accepts an array of octets as the next portion of + * the message. + * + * Parameters: + * context: [in/out] + * The SHA-1 context to update + * message_array: [in] + * An array of characters representing the next portion of the + * message. + * length: [in] + * The length of the message in message_array + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1Input( SHA1Context *context, + const unsigned char *message_array, + unsigned length) +{ + if (!length) + { + return; + } + + if (context->Computed || context->Corrupted) + { + context->Corrupted = 1; + return; + } + + while(length-- && !context->Corrupted) + { + context->Message_Block[context->Message_Block_Index++] = + (*message_array & 0xFF); + + context->Length_Low += 8; + /* Force it to 32 bits */ + context->Length_Low &= 0xFFFFFFFF; + if (context->Length_Low == 0) + { + context->Length_High++; + /* Force it to 32 bits */ + context->Length_High &= 0xFFFFFFFF; + if (context->Length_High == 0) + { + /* Message is too long */ + context->Corrupted = 1; + } + } + + if (context->Message_Block_Index == 64) + { + SHA1ProcessMessageBlock(context); + } + + message_array++; + } +} + +/* + * SHA1ProcessMessageBlock + * + * Description: + * This function will process the next 512 bits of the message + * stored in the Message_Block array. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * Many of the variable names in the SHAContext, especially the + * single character names, were used because those were the names + * used in the publication. + * + * + */ +void SHA1ProcessMessageBlock(SHA1Context *context) +{ + const unsigned K[] = /* Constants defined in SHA-1 */ + { + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; /* Loop counter */ + unsigned temp; /* Temporary word value */ + unsigned W[80]; /* Word sequence */ + unsigned A, B, C, D, E; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for(t = 0; t < 16; t++) + { + W[t] = ((unsigned) context->Message_Block[t * 4]) << 24; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]); + } + + for(t = 16; t < 80; t++) + { + W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + } + + A = context->Message_Digest[0]; + B = context->Message_Digest[1]; + C = context->Message_Digest[2]; + D = context->Message_Digest[3]; + E = context->Message_Digest[4]; + + for(t = 0; t < 20; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 20; t < 40; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 40; t < 60; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 60; t < 80; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + context->Message_Digest[0] = + (context->Message_Digest[0] + A) & 0xFFFFFFFF; + context->Message_Digest[1] = + (context->Message_Digest[1] + B) & 0xFFFFFFFF; + context->Message_Digest[2] = + (context->Message_Digest[2] + C) & 0xFFFFFFFF; + context->Message_Digest[3] = + (context->Message_Digest[3] + D) & 0xFFFFFFFF; + context->Message_Digest[4] = + (context->Message_Digest[4] + E) & 0xFFFFFFFF; + + context->Message_Block_Index = 0; +} + +/* + * SHA1PadMessage + * + * Description: + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 + * bits represent the length of the original message. All bits in + * between should be 0. This function will pad the message + * according to those rules by filling the Message_Block array + * accordingly. It will also call SHA1ProcessMessageBlock() + * appropriately. When it returns, it can be assumed that the + * message digest has been computed. + * + * Parameters: + * context: [in/out] + * The context to pad + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1PadMessage(SHA1Context *context) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (context->Message_Block_Index > 55) + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 64) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + + SHA1ProcessMessageBlock(context); + + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + else + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + context->Message_Block[56] = (context->Length_High >> 24) & 0xFF; + context->Message_Block[57] = (context->Length_High >> 16) & 0xFF; + context->Message_Block[58] = (context->Length_High >> 8) & 0xFF; + context->Message_Block[59] = (context->Length_High) & 0xFF; + context->Message_Block[60] = (context->Length_Low >> 24) & 0xFF; + context->Message_Block[61] = (context->Length_Low >> 16) & 0xFF; + context->Message_Block[62] = (context->Length_Low >> 8) & 0xFF; + context->Message_Block[63] = (context->Length_Low) & 0xFF; + + SHA1ProcessMessageBlock(context); +} diff --git a/sha1/sha1.h b/sha1/sha1.h new file mode 100644 index 00000000..1ca4b104 --- /dev/null +++ b/sha1/sha1.h @@ -0,0 +1,54 @@ +/* + * sha1.h + * + * Copyright (C) 1998, 2009 + * Paul E. Jones + * All Rights Reserved + * + ***************************************************************************** + * $Id: sha1.h 12 2009-06-22 19:34:25Z paulej $ + ***************************************************************************** + * + * Description: + * This class implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * Many of the variable names in the SHA1Context, especially the + * single character names, were used because those were the names + * used in the publication. + * + * Please read the file sha1.c for more information. + * + */ + +#ifndef _SHA1_H_ +#define _SHA1_H_ + +/* + * This structure will hold context information for the hashing + * operation + */ +typedef struct SHA1Context +{ + unsigned Message_Digest[5]; /* Message Digest (output) */ + + unsigned Length_Low; /* Message length in bits */ + unsigned Length_High; /* Message length in bits */ + + unsigned char Message_Block[64]; /* 512-bit message blocks */ + int Message_Block_Index; /* Index into message block array */ + + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corruped? */ +} SHA1Context; + +/* + * Function Prototypes + */ +void SHA1Reset(SHA1Context *); +int SHA1Result(SHA1Context *); +void SHA1Input( SHA1Context *, + const unsigned char *, + unsigned); + +#endif diff --git a/websocket.c b/websocket.c index e2e5b54a..7d73e1c3 100644 --- a/websocket.c +++ b/websocket.c @@ -1,4 +1,4 @@ -#include "md5/md5.h" +#include "sha1/sha1.h" #include "websocket.h" #include "client.h" #include "cmd.h" @@ -16,56 +16,36 @@ #include /** - * This code uses the WebSocket specification from May 23, 2010. - * The latest copy is available at http://www.whatwg.org/specs/web-socket-protocol/ + * This code uses the WebSocket specification from July, 2011. + * The latest copy is available at http://datatracker.ietf.org/doc/draft-ietf-hybi-thewebsocketprotocol/?include_text=1 */ -static uint32_t -ws_read_key(const char *s) { - - uint32_t ret = 0, spaces = 0; - const char *p; - size_t sz; - - if(!s) { - return 0; - } - - sz = strlen(s); - - for(p = s; p < s+sz; ++p) { - if(*p >= '0' && *p <= '9') { - ret *= 10; - ret += (*p) - '0'; - } else if (*p == ' ') { - spaces++; - } - } - return htonl(ret / spaces); -} static int -ws_compute_handshake(struct http_client *c, unsigned char *out) { +ws_compute_handshake(struct http_client *c, unsigned char *out, size_t *out_sz) { - char buffer[16]; - md5_state_t ctx; + unsigned char *buffer, sha1_output[20]; + char magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + SHA1Context ctx; // websocket handshake - uint32_t number_1 = ws_read_key(client_get_header(c, "Sec-WebSocket-Key1")); - uint32_t number_2 = ws_read_key(client_get_header(c, "Sec-WebSocket-Key2")); + const char *key = client_get_header(c, "Sec-WebSocket-Key"); + size_t key_sz = strlen(key), buffer_sz = key_sz + sizeof(magic) - 1; + buffer = calloc(key_sz, 1); - if(c->body_sz < 8) { /* we need at least 8 bytes */ - return -1; - } + // concatenate key and guid in buffer + memcpy(buffer, key, key_sz); + memcpy(buffer+key_sz, magic, sizeof(magic)-1); + + // compute sha-1 + SHA1Reset(&ctx); + SHA1Input(&ctx, buffer, buffer_sz); + SHA1Result(&ctx); + + memcpy(sha1_output, &ctx.Message_Digest[0], 20); - /* copy number_1, number_2, and last 8 bytes of the body. */ - memcpy(buffer, &number_1, 4); - memcpy(buffer + 4, &number_2, 4); - memcpy(buffer + 8, c->body + c->body_sz - 8, 8); - - /* hash that buffer, that creates the handshake signature. */ - md5_init(&ctx); - md5_append(&ctx, (const md5_byte_t *)buffer, sizeof(buffer)); - md5_finish(&ctx, out); + // TODO: encode `sha1_output' in base 64, into `out'. + *out = 0; + *out_sz = 0; return 0; } @@ -74,10 +54,10 @@ int ws_handshake_reply(struct http_client *c) { int ret; - unsigned char md5_handshake[16]; + unsigned char sha1_handshake[40]; char *buffer = NULL, *p; const char *origin = NULL, *host = NULL; - size_t origin_sz = 0, host_sz = 0, sz; + size_t origin_sz = 0, host_sz = 0, handshake_sz = 0, sz; char template0[] = "HTTP/1.1 101 Websocket Protocol Handshake\r\n" "Upgrade: WebSocket\r\n" @@ -101,7 +81,7 @@ ws_handshake_reply(struct http_client *c) { return -1; } - if(ws_compute_handshake(c, &md5_handshake[0]) != 0) { + if(ws_compute_handshake(c, &sha1_handshake[0], &handshake_sz) != 0) { /* failed to compute handshake. */ return -1; } @@ -109,7 +89,7 @@ ws_handshake_reply(struct http_client *c) { sz = sizeof(template0)-1 + origin_sz + sizeof(template1)-1 + host_sz + c->path_sz + sizeof(template2)-1 + host_sz - + sizeof(template3)-1 + sizeof(md5_handshake); + + sizeof(template3)-1 + handshake_sz + 1; p = buffer = malloc(sz); @@ -138,7 +118,7 @@ ws_handshake_reply(struct http_client *c) { /* template3 */ memcpy(p, template3, sizeof(template3)-1); p += sizeof(template3)-1; - memcpy(p, &md5_handshake[0], sizeof(md5_handshake)); + memcpy(p, &sha1_handshake[0], handshake_sz); ret = write(c->fd, buffer, sz); free(buffer);