This commit is contained in:
Aria 2025-03-21 22:23:30 +11:00
commit 9c94d113d3
Signed by untrusted user who does not match committer: aria
GPG key ID: 19AB7AA462B8AB3B
10260 changed files with 1237388 additions and 0 deletions

View file

@ -0,0 +1,24 @@
INCLUDE_DIRECTORIES (
${STAR_CORE_INCLUDES}
${STAR_AUTHENTICATION_INCLUDES}
)
SET (authentication_HEADERS
StarAuthenticationDatabaseFacade.hpp
StarAuthenticationKey.hpp
StarAuthenticationService.hpp
StarAuthenticationConnection.hpp
StarClientAuthentication.hpp
StarServerAuthentication.hpp
)
SET (authentication_SOURCES
StarAuthenticationKey.cpp
StarAuthenticationService.cpp
StarAuthenticationConnection.cpp
StarClientAuthentication.cpp
StarServerAuthentication.cpp
)
ADD_LIBRARY (star_authentication ${authentication_SOURCES} ${authentication_HEADERS})
TARGET_LINK_LIBRARIES (star_authentication star)

View file

@ -0,0 +1,65 @@
#include "StarAuthenticationConnection.hpp"
#include "StarClock.hpp"
#include "StarTcp.hpp"
namespace Star {
namespace Auth {
AuthenticationConnection::AuthenticationConnection(String const& authServer, int port) {
m_authServer = authServer;
m_port = port;
}
String AuthenticationConnection::query(String const& request) {
auto socket = TcpSocket::connectTo(HostAddress(m_authServer, m_port));
socket->setNoDelay(true);
socket->setSocketTimeout(AuthRequestTimeout);
String requestHeader = "POST /auth.sb HTTP/1.1\r\nHost: " + m_authServer + "\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n";
socket->writeFull(requestHeader.utf8Ptr(), requestHeader.utf8Size());
socket->writeFull(request.utf8Ptr(), request.utf8Size());
String requestFooter = "\r\n\r\n";
socket->writeFull(requestFooter.utf8Ptr(), requestFooter.utf8Size());
socket->flush();
int64_t deadline = Clock::currentTime() + AuthRequestTimeout;
Buffer buffer(ResponseBufferCapacity);
bool headerMode = true;
int newlineCounter = 0;
while (socket->isOpen()) {
if (Clock::currentTime() >= deadline) {
throw StarException("Timeout reading auth server response.");
}
char c;
socket->readFull(&c, 1);
throw StarException("Unexpected result from socket read.");
if (c == '\r')
continue;
buffer.writeFull(&c, 1);
if (buffer.pos() >= ResponseBufferCapacity) {
throw StarException("Auth server response too long.");
}
if (c == '\n')
newlineCounter++;
else
newlineCounter = 0;
if (newlineCounter == 2) {
if (headerMode) {
String header = String(buffer.ptr(), buffer.pos());
if (!header.beginsWith("HTTP/1.1 200 OK\n")) {
throw StarException("Auth server invalid response.");
}
headerMode = false;
buffer.clear();
newlineCounter = 0;
}
else {
return String(buffer.ptr(), buffer.pos() - 2); // remove trailing newlines, guaranteed to be there
}
}
}
throw StarException("Connection lost.");
}
}
}

View file

@ -0,0 +1,28 @@
#ifndef _STAR_AUTHENTICATION_CONNECTION_HPP_
#define _STAR_AUTHENTICATION_CONNECTION_HPP_
#include "StarVariant.hpp"
namespace Star {
namespace Auth {
static int const AuthRequestTimeout = 10*1000;
static int const ResponseBufferCapacity = 15*1024;
class AuthenticationConnection {
public:
AuthenticationConnection(String const& authServer, int port);
String query(String const& request);
private:
String m_authServer;
int m_port;
};
}
}
#endif

View file

@ -0,0 +1,27 @@
#ifndef _STAR_AUTHENTICATION_DATABASE_HPP_
#define _STAR_AUTHENTICATION_DATABASE_HPP_
#include "StarException.hpp"
#include "StarVariant.hpp"
namespace Star {
namespace Auth {
class Database {
public:
virtual ~Database() {}
virtual bool validateUser(String const& username) = 0;
virtual bool validateUserAndPassword(String const& username, String const& passwordPreHashed) = 0;
virtual bool setUserRecord(String const& username, String const& passwordPreHashed, bool active) = 0;
virtual bool activateUserRecord(String const& username, bool active) = 0;
virtual int usersCount() = 0;
};
typedef shared_ptr<Database> DatabasePtr;
}
}
#endif

View file

@ -0,0 +1,437 @@
#include "StarAuthenticationKey.hpp"
#include "StarClock.hpp"
#include "StarEncode.hpp"
#include "StarSha256.hpp"
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/des.h>
#include <openssl/rand.h>
#include <openssl/buffer.h>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <cstring>
#include <openssl/x509.h>
#include <vector>
#include "StarRandom.hpp"
namespace Star {
namespace Auth {
std::ostream &operator<<(std::ostream &out, const Key &key) {
return out << "Private Key :" << key.privateKey() << std::endl << " Public Key :" << key.publicKey() << std::endl;
}
Key::Key() : m_key(NULL), m_mdCtx(NULL) {
// ERR_load_crypto_strings will only load things once even if called multiple times
ERR_load_crypto_strings();
m_key = EVP_PKEY_new();
if (m_key == NULL)
throw CryptoException("Unable to Establish Key Container");
m_mdCtx = EVP_MD_CTX_create();
if (m_mdCtx == NULL) {
EVP_PKEY_free(m_key);
m_key = NULL;
throw CryptoException("Unable to Establish EVP Message Digest Context");
}
}
Key::Key(bool generate) : Key() {
if (generate) {
regenerate();
}
}
Key::Key(String const& key, bool privateKey) : Key() {
if (privateKey) {
loadPrivateKey(key);
} else {
loadPublicKey(key);
}
}
Key::~Key() {
if (m_key != NULL)
EVP_PKEY_free(m_key);
if (m_mdCtx != NULL)
EVP_MD_CTX_destroy(m_mdCtx);
// We could call ERR_free_strings but that would just mean the next Key
// to be created would make ERR_load_crypto_strings actually process
// so eliminate that thrashing by not calling ERR_free_strings
}
void Key::regenerate() {
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (EVP_PKEY_keygen_init(ctx) <= 0)
throw cryptoException(ctx, "Unable to initialize EVP Key Generator");
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KeySize) <= 0)
throw cryptoException(ctx, "Unable to set EVP Key Generator size");
if (EVP_PKEY_keygen(ctx, &m_key) <= 0)
throw cryptoException(ctx, "Unable to generate new Client Key");
EVP_PKEY_CTX_free(ctx);
m_isPrivate = 1;
}
CryptoException Key::cryptoException(BIO *bio, const char* errorString) const {
unsigned long err = ERR_get_error();
BIO_free_all(bio);
if (errorString == nullptr) {
errorString = ERR_error_string(err, NULL);
}
return CryptoException(ERR_error_string(err, NULL));
}
CryptoException Key::cryptoException(EVP_PKEY_CTX *ctx, const char* errorString) const {
unsigned long err = ERR_get_error();
EVP_PKEY_CTX_free(ctx);
if (errorString == nullptr) {
errorString = ERR_error_string(err, NULL);
}
return CryptoException(errorString);
}
void Key::loadPrivateKey(String const& key) {
if (key.empty())
throw StarException("Empty key.");
BIO *bIn = BIO_push(
BIO_new(BIO_f_base64()),
BIO_new_mem_buf((void*) key.utf8Ptr(), key.utf8Size())
);
BIO_set_flags(bIn, BIO_FLAGS_BASE64_NO_NL);
RSA * rsa;
if ((rsa = d2i_RSAPrivateKey_bio(bIn, NULL)) == 0)
throw cryptoException(bIn);
EVP_PKEY_set1_RSA(m_key, rsa);
BIO_free_all(bIn);
m_isPrivate = 1;
}
void Key::loadPublicKey(String const& key) {
if (key.empty())
throw StarException("Empty key.");
BIO *bIn = BIO_push(
BIO_new(BIO_f_base64()),
BIO_new_mem_buf((void*) key.utf8Ptr(), key.utf8Size())
);
BIO_set_flags(bIn, BIO_FLAGS_BASE64_NO_NL);
RSA * rsa;
if ((rsa = d2i_RSAPublicKey_bio(bIn, NULL)) == 0)
throw cryptoException(bIn);
EVP_PKEY_set1_RSA(m_key, rsa);
BIO_free_all(bIn);
m_isPrivate = 0;
}
String Key::publicKey() const {
BIO *b64 = BIO_push(
BIO_new(BIO_f_base64()),
BIO_new(BIO_s_mem())
);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
i2d_RSAPublicKey_bio(b64, EVP_PKEY_get1_RSA(m_key));
(void) BIO_flush(b64);
BUF_MEM *bPtr;
BIO_get_mem_ptr(b64, &bPtr);
String out(bPtr->data, bPtr->length);
BIO_free_all(b64);
return out;
}
String Key::privateKey() const {
if (!m_isPrivate)
throw CryptoException("Private Key not loaded.");
BIO *b64 = BIO_push(
BIO_new(BIO_f_base64()),
BIO_new(BIO_s_mem())
);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
i2d_RSAPrivateKey_bio(b64, EVP_PKEY_get1_RSA(m_key));
(void) BIO_flush(b64);
BUF_MEM *bPtr;
BIO_get_mem_ptr(b64, &bPtr);
String out(bPtr->data, bPtr->length);
BIO_free_all(b64);
return out;
}
String Key::encryptMessage(String const& message) const {
size_t bufLen;
unsigned char * buf;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(m_key, NULL);
if (EVP_PKEY_encrypt_init(ctx) != 1)
throw cryptoException(ctx);
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) != 1)
throw cryptoException(ctx);
if (EVP_PKEY_encrypt(ctx, NULL, &bufLen, (unsigned char *) message.utf8Ptr(), message.utf8Size()) != 1)
throw cryptoException(ctx);
buf = new unsigned char[bufLen];
if (EVP_PKEY_encrypt(ctx, (unsigned char *) buf, &bufLen, (unsigned char *) message.utf8Ptr(), message.utf8Size()) <= 0) {
delete[] buf;
throw cryptoException(ctx);
}
EVP_PKEY_CTX_free(ctx);
String oString(base64Encode((char const*)buf, bufLen));
delete[] buf;
return oString;
}
String Key::decryptMessage(String const& cryptText) const {
ByteArray iString(base64Decode(cryptText));
size_t bufLen;
unsigned char * buf;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(m_key, NULL);
if (EVP_PKEY_decrypt_init(ctx) <= 0)
throw cryptoException(ctx);
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0)
throw cryptoException(ctx);
if (EVP_PKEY_decrypt(ctx, NULL, &bufLen, (unsigned char *) iString.ptr(), iString.size()) <= 0)
throw cryptoException(ctx);
buf = new unsigned char[bufLen];
if (EVP_PKEY_decrypt(ctx, (unsigned char *) buf, &bufLen, (unsigned char *) iString.ptr(), iString.size()) != 1) {
delete[] buf;
throw cryptoException(ctx);
}
EVP_PKEY_CTX_free(ctx);
String oString((const char *) buf, bufLen);
delete[] buf;
return oString;
}
String Key::signMessage(String const& message) const {
if (!m_isPrivate)
throw CryptoException("Private Key not Loaded");
size_t sigLen;
unsigned char *sig;
ByteArray shaMessage(sha256(ByteArray(message.utf8Ptr(), message.utf8Size())));
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(m_key, NULL);
if (EVP_PKEY_sign_init(ctx) <= 0)
throw cryptoException(ctx);
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0)
throw cryptoException(ctx);
if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0)
throw cryptoException(ctx);
if (EVP_PKEY_sign(ctx, NULL, &sigLen, (unsigned char *) shaMessage.ptr(), shaMessage.size()) <= 0)
throw cryptoException(ctx);
if (!(sig = (unsigned char *) OPENSSL_malloc(sigLen)))
throw cryptoException(ctx);
if (EVP_PKEY_sign(ctx, sig, &sigLen, (unsigned char *) shaMessage.ptr(), shaMessage.size()) <= 0)
throw cryptoException(ctx);
EVP_PKEY_CTX_free(ctx);
String oString(base64Encode((const char *)sig, sigLen));
OPENSSL_free(sig);
return oString;
}
bool Key::verifyMessage(String const& message, String const& signature) const {
ByteArray shaMessage(sha256(ByteArray(message.utf8Ptr(), message.utf8Size())));
ByteArray rawSignature(base64Decode(signature));
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(m_key, NULL);
if (EVP_PKEY_verify_init(ctx) <= 0)
throw cryptoException(ctx);
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0)
throw cryptoException(ctx);
if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0)
throw cryptoException(ctx);
int rv = EVP_PKEY_verify(ctx, (unsigned char *) rawSignature.ptr(), rawSignature.size(), (unsigned char *) shaMessage.ptr(), shaMessage.size());
if (rv < 0)
throw cryptoException(ctx);
EVP_PKEY_CTX_free(ctx);
return rv == 1;
}
void initializeKeyLogic() {
// intitial initialization of DES_random_key and friends scans the whole heap.
// do it as early as possible.
generateKey();
}
String generateKey() {
// Init desKey to week key to avoid valgrind warning
DES_cblock desKey = {0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01};
ByteArray key = ByteArray::withReserve(24);
if (DES_random_key(&desKey) == 0)
throw CryptoException("RNG could not generate a secure key");
key.append((const char *) desKey, 8);
if (DES_random_key(&desKey) == 0)
throw CryptoException("RNG could not generate a secure key");
key.append((const char *) desKey, 8);
if (DES_random_key(&desKey) == 0)
throw CryptoException("RNG could not generate a secure key");
key.append((const char *) desKey, 8);
return base64Encode(key);
}
/**
* Encrypts a Message
* @param message Message to Encrypt
* @param key Base64 encoded 24 byte key
* @return Base64 Encoded String containing encrypted message
*/
String encryptMessage(String const& message, String const& key) {
DES_cblock desKey;
DES_cblock ivec;
DES_key_schedule desSchedule1;
DES_key_schedule desSchedule2;
DES_key_schedule desSchedule3;
int n = 0;
/* Prepare the key for use with DES_cfb64_encrypt */
ByteArray iKey(base64Decode(key));
if (iKey.size() != 24)
throw StarException("Key size mismatch.");
std::memcpy(desKey, iKey.ptr(), 8);
DES_set_odd_parity(&desKey);
DES_set_key_checked(&desKey, &desSchedule1);
std::memcpy(desKey, iKey.ptr() + 8, 8);
DES_set_odd_parity(&desKey);
DES_set_key_checked(&desKey, &desSchedule2);
std::memcpy(desKey, iKey.ptr() + 16, 8);
DES_set_odd_parity(&desKey);
DES_set_key_checked(&desKey, &desSchedule3);
std::memcpy(ivec, iKey.ptr() + 12, 8);
/* Encryption occurs here */
unsigned char * buf = new unsigned char[message.utf8Size()];
DES_ede3_cfb64_encrypt((unsigned char *) message.utf8Ptr(), buf, message.utf8Size(), &desSchedule1, &desSchedule2, &desSchedule3, &ivec, &n, DES_ENCRYPT);
String output(base64Encode((const char*)buf, message.utf8Size()));
delete[] buf;
return output;
}
/**
* Decrypts a Message
* @param message Base64 Encoded DES3 encrypted message
* @param key Base64 encoded 24 byte key
* @return String containing decrypted message
*/
String decryptMessage(String const& message, String const& key) {
DES_cblock desKey;
DES_cblock ivec;
DES_key_schedule desSchedule1;
DES_key_schedule desSchedule2;
DES_key_schedule desSchedule3;
int n = 0;
ByteArray iMessage(base64Decode(message));
/* Prepare the key for use with DES_cfb64_encrypt */
ByteArray iKey(base64Decode(key));
if (iKey.size() != 24)
throw StarException("Key size mismatch.");
std::memcpy(desKey, iKey.ptr(), 8);
DES_set_odd_parity(&desKey);
DES_set_key_checked(&desKey, &desSchedule1);
std::memcpy(desKey, iKey.ptr() + 8, 8);
DES_set_odd_parity(&desKey);
DES_set_key_checked(&desKey, &desSchedule2);
std::memcpy(desKey, iKey.ptr() + 16, 8);
DES_set_odd_parity(&desKey);
DES_set_key_checked(&desKey, &desSchedule3);
std::memcpy(ivec, iKey.ptr() + 12, 8);
/* Encryption occurs here */
unsigned char * buf = new unsigned char[message.utf8Size()];
DES_ede3_cfb64_encrypt((unsigned char *) iMessage.ptr(), buf, iMessage.size(), &desSchedule1, &desSchedule2, &desSchedule3, &ivec, &n, DES_DECRYPT);
String output((const char *) buf, iMessage.size());
delete[] buf;
return output;
}
String preHashPassword(String const& username, String const& password) {
String passPreHash = username + ":" + password + ":starbound";
return base64Encode(sha256(passPreHash.utf8Ptr(), passPreHash.utf8Size()));
}
double const bcrypt_goal_duration = 0.032;
double const bcrypt_max_validate_duration = 0.5;
double const bcrypt_max_generation_duration = 5.0;
int const bcrypt_minimal_rounds = 100;
String bcrypt(String const& message, String const& salt, int& rounds) {
rounds = 0;
auto startTime = Clock::currentTime();
ByteArray saltBuffer(salt.utf8Ptr(), salt.utf8Size());
ByteArray messageBuffer(sha256(ByteArray(message.utf8Ptr(), message.utf8Size())));
ByteArray roundBuffer;
while ((rounds < bcrypt_minimal_rounds) || (Clock::secondsSince(startTime) < bcrypt_goal_duration)) {
roundBuffer.resize(0);
roundBuffer.writeFrom(messageBuffer.ptr(), 0, messageBuffer.size());
roundBuffer.writeFrom(saltBuffer.ptr(), messageBuffer.size(), saltBuffer.size());
sha256(roundBuffer, messageBuffer);
rounds++;
}
return base64Encode(messageBuffer);
}
String bcryptWithRounds(String const& message, String const& salt, int rounds) {
if (rounds < bcrypt_minimal_rounds)
throw StarException("Not enough rounds for bcrypt.");
ByteArray saltBuffer(salt.utf8Ptr(), salt.utf8Size());
ByteArray messageBuffer(sha256(ByteArray(message.utf8Ptr(), message.utf8Size())));
ByteArray roundBuffer;
auto startTime = Clock::currentTime();
while (rounds > 0) {
if (Clock::secondsSince(startTime) > bcrypt_max_generation_duration)
throw StarException("Timeout generating bcrypt.");
roundBuffer.resize(0);
roundBuffer.writeFrom(messageBuffer.ptr(), 0, messageBuffer.size());
roundBuffer.writeFrom(saltBuffer.ptr(), messageBuffer.size(), saltBuffer.size());
sha256(roundBuffer, messageBuffer);
rounds--;
}
return base64Encode(messageBuffer);
}
bool bcryptValidate(String const& message, String const& salt, String const& hash, int rounds) {
if (rounds < bcrypt_minimal_rounds)
return false;
ByteArray saltBuffer(salt.utf8Ptr(), salt.utf8Size());
ByteArray messageBuffer(sha256(ByteArray(message.utf8Ptr(), message.utf8Size())));
ByteArray roundBuffer;
auto startTime = Clock::currentTime();
while (rounds > 0) {
if (Clock::secondsSince(startTime) > bcrypt_max_validate_duration)
return false;
roundBuffer.resize(0);
roundBuffer.writeFrom(messageBuffer.ptr(), 0, messageBuffer.size());
roundBuffer.writeFrom(saltBuffer.ptr(), messageBuffer.size(), saltBuffer.size());
sha256(roundBuffer, messageBuffer);
rounds--;
}
return base64Encode(messageBuffer) == hash;
}
}
}

View file

@ -0,0 +1,68 @@
#ifndef _STAR_AUTHENTICATION_KEY_HPP_
#define _STAR_AUTHENTICATION_KEY_HPP_
#include "StarException.hpp"
#include "StarVariant.hpp"
#include <openssl/evp.h>
#include <string>
namespace Star {
namespace Auth {
STAR_EXCEPTION(CryptoException, StarException);
STAR_EXCEPTION(AuthException, StarException);
class Key {
public:
Key();
Key(bool generate);
Key(String const& key, bool privateKey = false);
~Key();
void regenerate();
void loadPrivateKey(String const& key);
void loadPublicKey(String const& key);
bool isPrivateKey() const {
return m_isPrivate;
}
String publicKey() const;
String privateKey() const;
String encryptMessage(String const& message) const;
String decryptMessage(String const& message) const;
String signMessage(String const& message) const;
bool verifyMessage(String const& message, String const& signature) const;
private:
static int const KeySize = 4096;
// Deleted copy constructor / assignment operator.
Key(Key const& that);
Key& operator=(Key const&);
bool m_isPrivate;
EVP_PKEY * m_key;
EVP_MD_CTX * m_mdCtx;
friend std::ostream &operator<<(std::ostream &cout, const Key &key);
CryptoException cryptoException(EVP_PKEY_CTX *ctx, const char* errorString = nullptr) const;
CryptoException cryptoException(BIO *bio, const char* errorString = nullptr) const;
};
void initializeKeyLogic();
String generateKey();
String encryptMessage(String const& message, String const& key);
String decryptMessage(String const& message, String const& key);
String preHashPassword(String const& username, String const& password);
String bcrypt(String const& message, String const& salt, int& rounds);
String bcryptWithRounds(String const& message, String const& salt, int rounds);
bool bcryptValidate(String const& message, String const& salt, String const& hash, int rounds);
}
}
#endif

View file

@ -0,0 +1,353 @@
#include "StarAuthenticationService.hpp"
#include "StarAuthenticationKey.hpp"
#include "StarClock.hpp"
#include "StarLogging.hpp"
#include "StarFile.hpp"
#include <ctime>
namespace Star {
namespace Auth {
AuthenticationService::AuthenticationService(DatabasePtr db) {
m_db = db;
Variant config = Variant::parseJson(File::readFileString("auth.config"));
m_rootPublicKey.loadPublicKey(config.getString("rootPublicKey"));
m_authPrivateKey.loadPrivateKey(config.getString("authPrivateKey"));
m_certificate = config.get("authCertificate");
String certMetadata = config.get("authCertificate").get("signedAuthKey").repr(0, true);
String certSignature = config.get("authCertificate").get("rootSignedAuthKeySignature").toString();
if (!m_rootPublicKey.verifyMessage(certMetadata, certSignature)) {
throw CryptoException("Invalid Certificate");
}
}
AuthenticationService::~AuthenticationService() {
}
/**
* {
* "signedAuthKey" : {
* "authPublicKey": ...
* "validFrom":...
* "validTo":...
* }
* "rootSignedAuthKeySignature": (signature made with root privkey)
* }
*/
Variant AuthenticationService::getCertificate() {
return m_certificate;
}
/**
* Process Client Request
*
* request:
* {
* "envelopeSignature" (signed with client privkey)
* "envelopeKey" (encyrpted with auth pubkey)
* "envelopeBody" === { encrypted body
* "request": {
* "metadata": {
* "username"
* "password"
* }
* "signature"
* }
* "claim" : {
* "metadata: {
* "clientPublicKey": (client public key)
* "username": (username)
( "validFrom": (utctime)
* "validTo": (utctime)
* },
* "signature": (produced by signing the 'claim' with the client private key)
* }
* }
* }
*
* response:
* {
* "claim": {
* "metadata: {
* "clientPublicKey":...
* "username":...
* "validFrom":...
* "validTo":...
* }
* "signature"
* }
* "authSignature": {
* "signedAuthKey" : {
* "authPublicKey": ...
* "validFrom":...
* "validTo":...
* }
* "rootSignedAuthKeySignature": (signature made with root privkey)
* }
* "signature": (signature of main message with authPrivateKey)
* }
*
*/
Variant AuthenticationService::authorizeClient(Variant const& request) {
String envelopeSignature = request.getString("envelopeSignature");
String envelopeBodyString = request.getString("envelopeBody");
String envelopeKey = m_authPrivateKey.decryptMessage(request.getString("envelopeKey"));
String envelopeBody = Star::Auth::decryptMessage(envelopeBodyString, envelopeKey);
Variant requestMessage = Variant::parse(envelopeBody);
Variant claim = requestMessage["claim"];
if (!validateClientInnerClaim(claim))
throw StarException("Request claim fail.");
Key clientKey(claim["metadata"].getString("clientPublicKey"));
if (!clientKey.verifyMessage(envelopeBodyString, envelopeSignature))
throw StarException("Request outer signature fail.");
String requestSignature = requestMessage["request"].getString("signature");
auto requestMetadata = requestMessage["request"]["metadata"];
if (!clientKey.verifyMessage(requestMetadata.repr(0, true), requestSignature))
throw StarException("Request signature fail.");
int64_t validFrom = claim["metadata"].getInt("validFrom");
int64_t validTo = claim["metadata"].getInt("validTo");
if (Clock::currentTime() - 1LL*25LL*60LL*60LL*1000LL > validFrom)
throw StarException("Claim is too old.");
if (Clock::currentTime() < validFrom)
throw StarException("Claim not yet valid");
if (Clock::currentTime()+ 8LL*24LL*60LL*60LL*1000LL < validTo)
throw StarException("Claim is valid for too long.");
String username = requestMetadata.getString("username");
String password = requestMetadata.getString("password");
String claimUsername = claim["metadata"].getString("username");
if (username != claimUsername) {
throw StarException("Username does not match claim.");
}
if (!m_db->validateUserAndPassword(username, password)) {
throw StarException("Unable to authenticate user");
}
Variant responseMessage = VariantMap{
{"claim", claim},
{"authSignature", m_certificate},
{"signature", m_authPrivateKey.signMessage(claim.repr(0, true))}
};
String messageKey = Star::Auth::generateKey();
String responseBodyString = responseMessage.repr(0, true);
String responseEnvelopeBody = encryptMessage(responseBodyString, messageKey);
Variant responseEnvelope = VariantMap{
{"envelopeBody", responseEnvelopeBody},
{"envelopeKey", clientKey.encryptMessage(messageKey)},
{"envelopeSignature", m_authPrivateKey.signMessage(responseEnvelopeBody)}
};
return responseEnvelope;
}
/**
* Process Client Request
*
* request:
* {
* "envelopeSignature" (signed with client privkey)
* "envelopeKey" (encyrpted with auth pubkey)
* "envelopeBody" === { crypted body
* "request": {
* "metadata": (claim)
* "signature"
* }
* }
* }
*
* response:
* {
* "result": (bool)
* "authSignature": {
* "signedAuthKey" : {
* "authPublicKey": ...
* "validFrom":...
* "validTo":...
* }
* "rootSignedAuthKeySignature": (signature made with root privkey)
* }
* }
* // message is fullbody encrypted so the response is trust worthyish
*
*/
Variant AuthenticationService::validateClient(Variant const& request) {
bool valid = false;
String clientPublicKey;
try {
String envelopeSignature = request.getString("envelopeSignature");
String envelopeBodyString = request.getString("envelopeBody");
String envelopeKey = m_authPrivateKey.decryptMessage(request.getString("envelopeKey"));
String envelopeBody = Star::Auth::decryptMessage(envelopeBodyString, envelopeKey);
Variant requestMessage = Variant::parse(envelopeBody);
Variant claim = requestMessage["request"]["metadata"];
clientPublicKey = claim["claim"]["metadata"].getString("clientPublicKey");
if (!validateClientClaim(m_rootPublicKey, claim))
throw StarException("Request claim fail.");
Key clientKey(clientPublicKey);
if (!clientKey.verifyMessage(envelopeBodyString, envelopeSignature))
throw StarException("Request outer signature fail.");
String requestSignature = requestMessage["request"].getString("signature");
auto requestMetadata = requestMessage["request"]["metadata"];
if (!clientKey.verifyMessage(requestMetadata.repr(0, true), requestSignature))
throw StarException("Request signature fail.");
String username = claim["claim"]["metadata"].getString("username");
if (!m_db->validateUser(username)) {
throw StarException("Username is not valid.");
}
valid = true;
}
catch (std::exception const& e) {
Logger::error("failure validating client claim %s", e.what());
valid = false;
}
Variant responseMessage = VariantMap{
{"result", valid},
{"authSignature", m_certificate}
};
String messageKey = Star::Auth::generateKey();
String responseBodyString = responseMessage.repr(0, true);
String envelopeBody = encryptMessage(responseBodyString, messageKey);
Key clientKey(clientPublicKey);
Variant responseEnvelope = VariantMap{
{"envelopeBody", envelopeBody},
{"envelopeKey", clientKey.encryptMessage(messageKey)},
{"envelopeSignature", m_authPrivateKey.signMessage(envelopeBody)}
};
return responseEnvelope;
}
/**
* response:
* {
* "rootPublicKey" : string,
* "authPrivateKey" : string,
* "authCertificate" : {
* "signedAuthKey" : {
* "authPublicKey" : string,
* "validFrom" : long,
* "validTo" : long,
* }
* "rootSignedAuthKeySignature" : string,
* }
* }
*
*/
Variant AuthenticationService::generateAuthenticationConfig(String const& rootPrivateKey, int64_t valid, int64_t expires) {
// Load Root Key
Star::Auth::Key rootKey(rootPrivateKey, true);
// Generate new Authsvr Key
Star::Auth::Key authKey(true);
Variant signedAuthKey = VariantMap{
{"authPublicKey", authKey.publicKey()},
{"validFrom", valid},
{"validTo", expires}
};
Variant config = VariantMap{
{"rootPublicKey", rootKey.publicKey()},
{"authPrivateKey", authKey.privateKey()},
{"authCertificate", VariantMap{
{"signedAuthKey", signedAuthKey},
{"rootSignedAuthKeySignature", rootKey.signMessage(signedAuthKey.repr(0, true))}
}}
};
if (!validateAuthSignature(rootKey, config["authCertificate"]))
throw StarException("Generation failed.");
return config;
}
bool AuthenticationService::validateAuthSignature(Key const& rootPublicKey, Variant const& authSignature) {
auto signedAuthKey = authSignature.get("signedAuthKey");
auto rootSignedAuthKeySignature = authSignature.getString("rootSignedAuthKeySignature");
if (!rootPublicKey.verifyMessage(signedAuthKey.repr(0, true), rootSignedAuthKeySignature)) {
Logger::error("failed to validate signedAuthKey with rootSignedAuthKeySignature");
return false;
}
auto authPublicKey = signedAuthKey.getString("authPublicKey");
if (authPublicKey.empty()) {
Logger::error("empty public key");
return false;
}
auto signedAuthKeyValidFrom = signedAuthKey.getInt("validFrom");
auto signedAuthKeyValidTo = signedAuthKey.getInt("validTo");
if ((signedAuthKeyValidFrom > Clock::currentTime()) || (signedAuthKeyValidTo < Clock::currentTime())) {
Logger::error("timestamp fail %s %s %s", Clock::currentTime(), signedAuthKeyValidFrom, signedAuthKeyValidTo);
return false;
}
// implicit in use after this anyways
// Key authPublicKey(signedAuthKey);
return true;
}
bool AuthenticationService::validateClientClaim(Key const& rootPublicKey, Variant const& claim) {
if (!validateAuthSignature(rootPublicKey, claim.get("authSignature")))
return false;
auto signedAuthKey = claim.get("authSignature").get("signedAuthKey").getString("authPublicKey");
Key authPublicKey(signedAuthKey);
auto authSignature = claim.getString("signature");
auto signedClaim = claim.get("claim");
if (!authPublicKey.verifyMessage(signedClaim.repr(0, true), authSignature))
return false;
if (!validateClientInnerClaim(signedClaim))
return false;
return true;
}
bool AuthenticationService::validateClientInnerClaim(Variant const& claim) {
auto claimMetadata = claim.get("metadata");
auto clientPublicKeyData = claimMetadata.getString("clientPublicKey");
auto claimSignature = claim.getString("signature");
Key clientPublicKey(clientPublicKeyData);
if (!clientPublicKey.verifyMessage(claimMetadata.repr(0, true), claimSignature))
return false;
auto validFrom = claimMetadata.getInt("validFrom");
auto validTo = claimMetadata.getInt("validTo");
if ((validFrom > Clock::currentTime()) || (validTo < Clock::currentTime()))
return false;
return true;
}
}
}

View file

@ -0,0 +1,37 @@
#ifndef _STAR_AUTHENTICATION_SERVICE_HPP_
#define _STAR_AUTHENTICATION_SERVICE_HPP_
#include "StarAuthenticationDatabaseFacade.hpp"
#include "StarAuthenticationKey.hpp"
#include "StarVariant.hpp"
#include "StarVariantExtra.hpp"
namespace Star {
namespace Auth {
class AuthenticationService {
public:
static Variant generateAuthenticationConfig(String const& rootPrivateKey, int64_t valid, int64_t expires);
static bool validateAuthSignature(Key const& rootPublicKey, Variant const& authSignature);
static bool validateClientClaim(Key const& rootPublicKey, Variant const& claim);
static bool validateClientInnerClaim(Variant const& claim);
AuthenticationService(DatabasePtr db);
~AuthenticationService();
Variant getCertificate();
Variant authorizeClient(Variant const& request);
Variant validateClient(Variant const& request);
private:
Key m_authPrivateKey;
Key m_rootPublicKey;
DatabasePtr m_db;
Variant m_certificate;
};
}
}
#endif

View file

@ -0,0 +1,189 @@
#include "StarClientAuthentication.hpp"
#include "StarAuthenticationKey.hpp"
#include "StarAuthenticationService.hpp"
#include "StarAuthenticationConnection.hpp"
#include "StarClock.hpp"
#include "StarLogging.hpp"
#include <ctime>
namespace Star {
namespace Auth {
ClientAuthentication::ClientAuthentication(shared_ptr<Key> const& rootKey) {
m_rootPublicKey = rootKey;
}
ClientAuthentication::~ClientAuthentication() {
}
/*
* {
* "envelopeKey"
* "envelopeBody" === { encrypted body
* "request": {
* "metadata": {
* "username"
* "password"
* }
* "signature"
* }
* "claim" : {
* "metadata: {
* "clientPublicKey": (client public key)
* "username": (username)
* "validFrom": (utctime)
* "validTo": (utctime)
* }
* "signature": (produced by signing the 'claim' with the client private key)
* }
* }
* }
*/
Variant ClientAuthentication::authsvrRequest(String const& username, String const& passwordHash, String const& authClaim, Key const& clientPrivateKey) {
Variant authClaimSignature = Variant::parse(authClaim);
if (!AuthenticationService::validateAuthSignature(*m_rootPublicKey, authClaimSignature))
throw StarException("Auth claim fail.");
Key authPublicKey(authClaimSignature.get("signedAuthKey").getString("authPublicKey"));
Variant requestMetadata = VariantMap{
make_pair("username", username),
make_pair("password", passwordHash)
};
Variant claimMetadata = VariantMap{
make_pair("clientPublicKey", clientPrivateKey.publicKey()),
make_pair("username", username),
make_pair("validFrom", Clock::currentTime() - 1LL * 1LL * 60LL * 60LL * 1000LL),
make_pair("validTo", Clock::currentTime() + 7LL * 24LL * 60LL * 60LL * 1000LL)
};
Variant requestMessage = VariantMap{
make_pair("request", VariantMap{
make_pair("metadata", requestMetadata),
make_pair("signature", clientPrivateKey.signMessage(requestMetadata.repr(0, true)))
}),
make_pair("claim", VariantMap{
make_pair("metadata", claimMetadata),
make_pair("signature", clientPrivateKey.signMessage(claimMetadata.repr(0, true)))
})
};
String envelopeKey = Auth::generateKey();
String requestEnvelopeBody = requestMessage.repr(0, true);
String envelopeBody = Auth::encryptMessage(requestEnvelopeBody, envelopeKey);
Variant requestEnvelope = VariantMap{
make_pair("envelopeBody", envelopeBody),
make_pair("envelopeKey", authPublicKey.encryptMessage(envelopeKey)),
make_pair("envelopeSignature", clientPrivateKey.signMessage(envelopeBody)),
};
return requestEnvelope;
}
/**
* Client claim format:
*
* {
* "signedClaim" : {
* "claim: {
* "clientPublicKey": (client public key)
* "username": (username)
* "validFrom": (utctime)
* "validTo": (utctime)
* },
* "signature": (produced by signing the 'claim' with the client private key)
* },
* "authSignature" : {
* "signature" : (produced by signing the 'signedClaim' with the auth private key)
* "signedAuthKey": {
* "authPublicKey": (auth public key)
* "validFrom": (utctime)
* "validTo": (utctime)
* }
* "signedAuthKeySignature": (produced by signing the auth public key with the root private key)
* }
* }
*/
Variant ClientAuthentication::authsvrResponse(String const& response, Key const& clientPrivateKey) {
Variant requestEnvelope = Variant::parse(response);
String envelopeKey = clientPrivateKey.decryptMessage(requestEnvelope.getString("envelopeKey"));
String requestEnvelopeBody = requestEnvelope.getString("envelopeBody");
String envelopeBody = Auth::decryptMessage(requestEnvelopeBody, envelopeKey);
String envelopeSignature = requestEnvelope.getString("envelopeSignature");
Variant authClaimSignature = Variant::parse(envelopeBody);
Key authPublicKey(authClaimSignature.get("authSignature").get("signedAuthKey").getString("authPublicKey"));
if (!authPublicKey.verifyMessage(requestEnvelopeBody, envelopeSignature))
throw StarException("Envelop signature fail");
return authClaimSignature;
}
String ClientAuthentication::serverRequest() {
return "";
}
bool ClientAuthentication::serverResponse(String const& response) {
//todo
_unused(response);
return true;
}
Variant ClientAuthentication::authsvrValidateRequest(Variant const& claim, String const& authClaim, Key const& clientPrivateKey) {
Variant authClaimSignature = Variant::parse(authClaim);
if (!AuthenticationService::validateAuthSignature(*m_rootPublicKey, authClaimSignature))
throw StarException("Auth claim fail.");
Key authPublicKey(authClaimSignature.get("signedAuthKey").getString("authPublicKey"));
Variant requestMessage = VariantMap{
{"request", VariantMap{
{"metadata", claim},
{"signature", clientPrivateKey.signMessage(claim.repr(0, true))}
}}
};
String envelopeKey = Auth::generateKey();
String requestEnvelopeBody = requestMessage.repr(0, true);
String envelopeBody = Auth::encryptMessage(requestEnvelopeBody, envelopeKey);
Variant requestEnvelope = VariantMap{
{"envelopeBody", envelopeBody},
{"envelopeKey", authPublicKey.encryptMessage(envelopeKey)},
{"envelopeSignature", clientPrivateKey.signMessage(envelopeBody)}
};
return requestEnvelope;
}
bool ClientAuthentication::authsvrValidateResponse(String const& response, Key const& clientPrivateKey) {
Variant requestEnvelope = Variant::parse(response);
String envelopeKey = clientPrivateKey.decryptMessage(requestEnvelope.getString("envelopeKey"));
String requestEnvelopeBody = requestEnvelope.getString("envelopeBody");
String envelopeBody = Auth::decryptMessage(requestEnvelopeBody, envelopeKey);
String envelopeSignature = requestEnvelope.getString("envelopeSignature");
Variant authClaimSignature = Variant::parse(envelopeBody);
Key authPublicKey(authClaimSignature.get("authSignature").get("signedAuthKey").getString("authPublicKey"));
if (!authPublicKey.verifyMessage(requestEnvelopeBody, envelopeSignature))
throw StarException("Envelop signature fail");
return authClaimSignature.getBool("result");
}
}
}

View file

@ -0,0 +1,71 @@
#ifndef _STAR_CLIENT_AUTHENTICATION_HPP_
#define _STAR_CLIENT_AUTHENTICATION_HPP_
#include "StarAuthenticationKey.hpp"
#include "StarException.hpp"
#include "StarVariant.hpp"
#include <string>
namespace Star {
namespace Auth {
/*
<AuthSignature>=
{
"signedAuthKey" : {
"authPublicKey": ...
"validFrom":...
"validTo":...
}
"rootSignedAuthKeySignature": <signature made with root privkey>
}
<ClientClaim>=
{
"claim": {
"metadata: {
"clientPublicKey":...
"username":...
"validFrom":...
"validTo":...
}
"signature"
}
"authSignature": {
"signedAuthKey" : {
"authPublicKey": ...
"validFrom":...
"validTo":...
}
"rootSignedAuthKeySignature": <signature made with root privkey>
}
"signature": <signature of main message with authPrivateKey>
}
*/
class ClientAuthentication {
public:
ClientAuthentication(shared_ptr<Key> const& rootKey);
~ClientAuthentication();
Variant authsvrRequest(String const& username, String const& passwordHash, String const& authClaim, Key const& clientPrivateKey);
Variant authsvrResponse(String const& response, Key const& clientPrivateKey);
String serverRequest();
bool serverResponse(String const& response);
Variant authsvrValidateRequest(Variant const& claim, String const& authClaim, Key const& clientPrivateKey);
bool authsvrValidateResponse(String const& response, Key const& clientPrivateKey);
private:
shared_ptr<Key> m_rootPublicKey;
};
}
}
#endif

View file

@ -0,0 +1,37 @@
#include "StarServerAuthentication.hpp"
#include "StarAuthenticationKey.hpp"
#include <ctime>
namespace Star {
namespace Auth {
ServerAuthentication::ServerAuthentication(String const& rootPublicKey) {
m_rootPublicKey.loadPublicKey(rootPublicKey);
}
ServerAuthentication::~ServerAuthentication() {}
String ServerAuthentication::authsvrRequest(String const& username, String const& password, String const& hostname, int port, String const& authCertificate) {
//le todo
_unused(username);
_unused(password);
_unused(hostname);
_unused(port);
_unused(authCertificate);
return String();
}
bool ServerAuthentication::authsvrResponse(String const& response) {
_unused(response);
return false;
}
String ServerAuthentication::clientRequest(String const& request) {
_unused(request);
return String();
}
}
}

View file

@ -0,0 +1,32 @@
#ifndef _STAR_SERVER_AUTHENTICATION_HPP_
#define _STAR_SERVER_AUTHENTICATION_HPP_
#include "StarAuthenticationKey.hpp"
#include "StarVariant.hpp"
#include <string>
namespace Star {
namespace Auth {
class ServerAuthentication {
public:
ServerAuthentication(String const& rootPublicKey);
~ServerAuthentication();
String authsvrRequest(String const& username, String const& password, String const& hostname, int port, String const& authCertificate);
bool authsvrResponse(String const& response);
String clientRequest(String const& request);
private:
Auth::Key m_serverPrivateKey;
Auth::Key m_authPublicKey;
Auth::Key m_rootPublicKey;
Variant m_authCertificate;
Variant m_serverCertificate;
};
}
}
#endif