From 8c2fdff4c8feccda263f92fcc23019e1c465c448 Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Mon, 4 Nov 2013 14:01:31 +0200 Subject: [PATCH] dh25519 HTTP auth class --- include/library/httpauth.hpp | 91 ++++++++++++ src/library/httpauth.cpp | 264 +++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+) create mode 100644 include/library/httpauth.hpp create mode 100644 src/library/httpauth.cpp diff --git a/include/library/httpauth.hpp b/include/library/httpauth.hpp new file mode 100644 index 00000000..ab25dfad --- /dev/null +++ b/include/library/httpauth.hpp @@ -0,0 +1,91 @@ +#ifndef _library__httpauth__hpp__included__ +#define _library__httpauth__hpp__included__ + +#include "curve25519.hpp" +#include "skein.hpp" +#include +#include + +/** + * DH25519 HTTP auth class. + */ +class dh25519_http_auth +{ +public: +/** + * Internal hashing instance. + */ + class request_hash + { + public: +/** + * Construct. + */ + request_hash(const std::string& _id, const uint8_t* _key, unsigned _nonce, skein_hash _h, + const uint8_t* _prereq) + : id(_id), nonce(_nonce), h(_h) + { + memcpy(pubkey, _key, 32); + memcpy(prereq, _prereq, 8); + } +/** + * Append data to hash. + */ + void hash(const uint8_t* data, size_t datalen) + { + h.write(data, datalen); + } +/** + * Read the final Authorization header. + */ + std::string get_authorization(); + private: + std::string id; + uint8_t pubkey[32]; + uint8_t prereq[8]; + unsigned nonce; + skein_hash h; + }; +/** + * Create a new instance. + * + * Parameter privkey: The private key (32 bytes). + */ + dh25519_http_auth(const uint8_t* privkey); +/** + * Format a session creation request + * + * Returns: The value for Authorization header. + */ + std::string format_get_session_request(); +/** + * Start request hash computation. Hashes in the shared secret and nonce. The nonce is incremented. + * + * Parameter url: The notional URL. + * Returns: The skein hash instance. + */ + request_hash start_request(const std::string& url, const std::string& verb); +/** + * Parse session auth response. If it contains new session parameters, the session is updated. + * + * Parameter response: The response from server (WWW-Authenticate). + */ + void parse_auth_response(const std::string& response); +/** + * Is the session ready? + */ + bool is_ready() { return ready; } +/** + * Output pubkey. + */ + void get_pubkey(uint8_t* pubkey); +private: + unsigned char privkey[32]; + unsigned char pubkey[32]; + unsigned char ssecret[32]; + std::string id; + unsigned nonce; + bool ready; //id&ssecret is valid. +}; + +#endif diff --git a/src/library/httpauth.cpp b/src/library/httpauth.cpp new file mode 100644 index 00000000..088925c6 --- /dev/null +++ b/src/library/httpauth.cpp @@ -0,0 +1,264 @@ +#include "httpauth.hpp" +#include "string.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + std::string encode_hex(const uint8_t* data, size_t datasize) + { + std::ostringstream x; + for(size_t i = 0; i < datasize; i++) + x << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; + return x.str(); + } + + //Can only handle 9, 32-126, 128-255. + std::string quote_field(const std::string& field) + { + std::ostringstream x; + for(size_t i = 0; i < field.length(); i++) { + if(field[i] == '\\') + x << "\\\\"; + else if(field[i] == '\"') + x << "\\\""; + else + x << field[i]; + } + return x.str(); + } + + //Identity transform. + std::string identity(const std::string& field) + { + return field; + } + + //Character class. + //0 => Controls, except HT + //1 => Whitespace (HT and SP). + //2 => Token chars (!#$%&'*+.^_`|~0-9A-Za-z-) + //3 => Double quote (") + //4 => Other quoted characters. + //5 => Comma + //6 => Equals sign. + //7 => Backslash + //8 => EOS. + uint8_t charclass[] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, //0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //1 + 1, 2, 3, 2, 2, 2, 2, 2, 4, 4, 2, 2, 5, 2, 2, 4, //2 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 6, 4, 4, //3 + 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, //4 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 7, 4, 2, 2, //5 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, //6 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 4, 2, 0, //7 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //8 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //9 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //A + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //B + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //C + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //D + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //E + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 //F + }; + +#define A_INVALID 0x0000 //Give up. +#define A_NOOP 0x1000 //Do nothing, not even eat the character. +#define A_EAT 0x2000 //Eat the character. +#define A_COPY_PNAME 0x3000 //Eat and copy to pname. +#define A_COPY_PVAL 0x4000 //Eat and copy to pvalue. +#define A_EMIT 0x5000 //Emit (pname,pvalue) and zeroize. +#define A_MASK 0xF000 +#define A_STATE 0x0FFF + + unsigned auth_param_parser[] = { + //CTRL WS TOKN DBLQ OTHQ COMM EQLS BCKS EOS + // 0: Skip the initial whitespace. + 0x0000, 0x2000, 0x1001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2000, + // 1: Parse name. + 0x0000, 0x1002, 0x3001, 0x0000, 0x0000, 0x0000, 0x1002, 0x0000, 0x0000, + // 2: Parse whitespace after name (and =) + 0x0000, 0x2002, 0x0000, 0x0000, 0x0000, 0x0000, 0x2003, 0x0000, 0x0000, + // 3: Parse whitespace before value. + 0x0000, 0x2003, 0x1004, 0x2005, 0x0000, 0x5007, 0x0000, 0x0000, 0x5000, + // 4: Token value. + 0x0000, 0x5007, 0x4004, 0x0000, 0x0000, 0x5007, 0x0000, 0x0000, 0x5000, + // 5: Quoted-string value. + 0x0000, 0x4005, 0x4005, 0x5009, 0x4005, 0x4005, 0x4005, 0x2006, 0x0000, + // 6: Quoted-string escape. + 0x0000, 0x4005, 0x4005, 0x4005, 0x4005, 0x4005, 0x4005, 0x4005, 0x0000, + // 7: Whitespace after end of value. + 0x0000, 0x2007, 0x0000, 0x0000, 0x0000, 0x2008, 0x0000, 0x0000, 0x2000, + // 8: Whitespace after comma. + 0x0000, 0x2008, 0x1001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + // 9: Eat double quote at end of quoted string. + 0x0000, 0x0000, 0x0000, 0x2007, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + }; + + unsigned get_charclass(const std::string& str, size_t pos) { + if(pos < str.length()) + return charclass[(uint8_t)str[pos]]; + else + return 8; + } + + bool do_state_machine(const unsigned* machine, const std::string& input, size_t start, + std::map& params) + { + std::string pname, pvalue; + unsigned state = 0; + while(start <= input.length()) { + unsigned act = machine[state * 9 + get_charclass(input, start)]; + switch(act & A_MASK) { + case A_INVALID: + return false; + case A_NOOP: + break; + case A_EAT: + start++; + break; + case A_COPY_PNAME: + pname = pname + std::string(1, input[start++]); + break; + case A_COPY_PVAL: + pvalue = pvalue + std::string(1, input[start++]); + break; + case A_EMIT: + params[pname] = pvalue; + pname = ""; + pvalue = ""; + break; + }; + state = act & A_STATE; + } + return true; + } + + //Parse a hex char. + inline uint8_t hparse(char _ch) + { + uint8_t ch = _ch; + uint8_t itbl[] = {9,1,16,2,10,3,11,4,12,5,13,6,14,7,15,8,0}; + return itbl[(uint8_t)(2*ch + 22*(ch>>5)) % 17]; + } + + //Undo hex encoding. + void unhex(uint8_t* buf, const std::string& str) + { + bool polarity = false; + size_t ptr = 0; + uint8_t val = 0; + while(ptr < str.length()) { + val = val * 16 + hparse(str[ptr++]); + if(!(polarity = !polarity)) *(buf++) = val; + } + } +} + +dh25519_http_auth::dh25519_http_auth(const uint8_t* _privkey) +{ + memcpy(privkey, _privkey, 32); + curve25519_clamp(privkey); + curve25519(pubkey, privkey, curve25519_base); + ready = false; +} + +std::string dh25519_http_auth::format_get_session_request() +{ + return "dh25519 key="+encode_hex(pubkey,32); +} + +dh25519_http_auth::request_hash dh25519_http_auth::start_request(const std::string& url, const std::string& verb) +{ + unsigned _nonce; + std::string personalization = verb + " " + url; + char buf[32]; + uint8_t prereq[8]; + if(!ready) + throw std::runtime_error("Authenticator is not ready for request auth"); + _nonce = nonce++; + sprintf(buf, "%u", _nonce); + + skein_hash hp(skein_hash::PIPE_512, 64); + hp.write(ssecret, 32, skein_hash::T_KEY); + hp.write((const uint8_t*)personalization.c_str(), personalization.length(), skein_hash::T_PERSONALIZATION); + hp.write(pubkey, 32, skein_hash::T_PUBKEY); + hp.write((uint8_t*)buf, strlen(buf), skein_hash::T_NONCE); + hp.read(prereq); + + skein_hash h(skein_hash::PIPE_512, 256); + h.write(ssecret, 32, skein_hash::T_KEY); + h.write((const uint8_t*)personalization.c_str(), personalization.length(), skein_hash::T_PERSONALIZATION); + h.write(pubkey, 32, skein_hash::T_PUBKEY); + h.write((uint8_t*)buf, strlen(buf), skein_hash::T_NONCE); + return request_hash(id, pubkey, _nonce, h, prereq); +} + +std::string dh25519_http_auth::request_hash::get_authorization() +{ + char buf[32]; + uint8_t response[32]; + sprintf(buf, "%u", nonce); + h.read(response); + return "dh25519 id="+quote_field(id)+",key="+encode_hex(pubkey,32)+",nonce="+identity(buf)+ + ",response="+encode_hex(response,32)+",response2="+encode_hex(prereq,8); +} + +void dh25519_http_auth::parse_auth_response(const std::string& response) +{ + std::map pparse; + if(response.substr(0, 7) != "dh25519") return; + + if(!do_state_machine(auth_param_parser, response, 7, pparse)) { + throw std::runtime_error("Response parse error: <"+response+">"); + } + + //If there are id and challenge fields, use those to reseed. + bool stale = (pparse.count("error") && pparse["error"] == "stale"); + bool reseeded = false; + if(pparse.count("id") && pparse.count("challenge") && (!ready || pparse["id"] != id || stale)) { + id = pparse["id"]; + std::string challenge = pparse["challenge"]; + if(challenge.length() != 64) goto no_reseed; + uint8_t _challenge[32]; + unhex(_challenge, challenge); + curve25519(ssecret, privkey, _challenge); + nonce = 0; + reseeded = true; + ready = true; + } +no_reseed: + if(pparse.count("error")) { + if(pparse["error"] == "ok") + ; //Do nothing. + else if(pparse["error"] == "stale") { + //This is only an error if not reseeded this round. + if(!reseeded) { + ready = false; + throw std::runtime_error("Authentication is stale"); + } + } else if(pparse["error"] == "badkey") { + ready = false; + throw std::runtime_error("Client key not registed with target site"); + } else if(pparse["error"] == "badmac") + throw std::runtime_error("Request failed MAC check"); + else if(pparse["error"] == "replay") + throw std::runtime_error("Request was replayed"); + else if(pparse["error"] == "syntaxerr") + throw std::runtime_error("Request syntax error"); + else + throw std::runtime_error("Unknown error '" + pparse["error"] + "'"); + } +} + +void dh25519_http_auth::get_pubkey(uint8_t* _pubkey) +{ + memcpy(_pubkey, pubkey, 32); +}