#include "StarAuthenticationKey.hpp" #include "StarClock.hpp" #include "StarEncode.hpp" #include "StarSha256.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; } } }