Starbound/attic/authentication/StarAuthenticationService.cpp
2025-03-21 22:23:30 +11:00

353 lines
11 KiB
C++

#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;
}
}
}