dh25519 HTTP auth class

This commit is contained in:
Ilari Liusvaara 2013-11-04 14:01:31 +02:00
parent 1ac35c2773
commit 8c2fdff4c8
2 changed files with 355 additions and 0 deletions

View file

@ -0,0 +1,91 @@
#ifndef _library__httpauth__hpp__included__
#define _library__httpauth__hpp__included__
#include "curve25519.hpp"
#include "skein.hpp"
#include <string>
#include <cstring>
/**
* 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

264
src/library/httpauth.cpp Normal file
View file

@ -0,0 +1,264 @@
#include "httpauth.hpp"
#include "string.hpp"
#include <cstdint>
#include <cstring>
#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <map>
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<std::string, std::string>& 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<std::string,std::string> 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);
}