dh25519 HTTP auth class
This commit is contained in:
parent
1ac35c2773
commit
8c2fdff4c8
2 changed files with 355 additions and 0 deletions
91
include/library/httpauth.hpp
Normal file
91
include/library/httpauth.hpp
Normal 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
264
src/library/httpauth.cpp
Normal 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);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue