/*
* Steam Mobile Plugin for Pidgin
* Copyright (C) 2012-2016 Eion Robb
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
https://steamcommunity.com/mobilelogin/getrsakey?username=
{"success":true,"publickey_mod":"pubkeyhex","publickey_exp":"pubkeyhex","timestamp":"165685150000"}
https://steamcommunity.com/mobilelogin/dologin/
password=&username=&emailauth=&captchagid=-1&captcha_text=&emailsteamid=&rsatimestamp=165685150000&remember_login=true&donotcache=1368831657863
*/
#if defined USE_OPENSSL_CRYPTO && !(defined __APPLE__ || defined __OpenBSD__)
# undef USE_OPENSSL_CRYPTO
#endif
#if !defined USE_MBEDTLS_CRYPTO && !defined USE_OPENSSL_CRYPTO && !defined USE_NSS_CRYPTO && !defined USE_GCRYPT_CRYPTO
// # ifdef _WIN32
// # define USE_WIN32_CRYPTO
// # else
# define USE_NSS_CRYPTO
// # endif
#endif
#ifdef USE_NSS_CRYPTO
#include
#include
#include
#include
#include
// Coverts a hex string, eg "ABCD0123" into "\xAB\xCD\x01\x23"
// The length of the returned char* will always be half of that of the input string
guchar *
hexstring_to_binary(const gchar *in_string) {
guint in_len = strlen(in_string);
unsigned char *output;
guint pos, count;
guint output_len;
output_len = in_len / 2;
output = g_new0(unsigned char, output_len + 10);
pos = 0;
for(count = 0; count < output_len; count++) {
sscanf(&in_string[pos], "%2hhx", &output[count]);
pos += 2;
}
return output;
}
guchar *
pkcs1pad2(const char *data, int keysize)
{
guchar *buffer = g_new0(guchar, keysize);
int len = strlen(data) - 1;
while(len >=0 && keysize > 0)
buffer[--keysize] = (unsigned char)data[len--];
buffer[--keysize] = 0;
srand( time(NULL) );
while(keysize > 2)
buffer[--keysize] = (rand() % 254) + 1;
buffer[--keysize] = 2;
buffer[--keysize] = 0;
return buffer;
}
gchar *
steam_encrypt_password(const gchar *modulus_str, const gchar *exponent_str, const gchar *password)
{
SECItem derPubKey;
SECKEYPublicKey *pubKey;
PRArenaPool *arena;
guint modlen = strlen(modulus_str) / 2;
guint explen = strlen(exponent_str) / 2;
guchar *temp;
gchar *output;
guchar *encrypted;
//gchar *tmpstr;
struct MyRSAPublicKey {
SECItem m_modulus;
SECItem m_exponent;
} inPubKey;
SECStatus rv;
const SEC_ASN1Template MyRSAPublicKeyTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(struct MyRSAPublicKey) },
{ SEC_ASN1_INTEGER, offsetof(struct MyRSAPublicKey, m_modulus), },
{ SEC_ASN1_INTEGER, offsetof(struct MyRSAPublicKey, m_exponent), },
{ 0, }
};
temp = hexstring_to_binary(modulus_str);
inPubKey.m_modulus.data = (unsigned char *) PORT_Alloc(modlen + 10);
memcpy(inPubKey.m_modulus.data, temp, modlen);
inPubKey.m_modulus.len = modlen;
inPubKey.m_modulus.type = siUnsignedInteger;
g_free(temp);
temp = hexstring_to_binary(exponent_str);
inPubKey.m_exponent.data = (unsigned char *) PORT_Alloc(explen + 10);
memcpy(inPubKey.m_exponent.data, temp, explen);
inPubKey.m_exponent.len = explen;
inPubKey.m_exponent.type = siUnsignedInteger;
g_free(temp);
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
SEC_ASN1EncodeItem(arena, &derPubKey, &inPubKey, MyRSAPublicKeyTemplate);
pubKey = SECKEY_ImportDERPublicKey(&derPubKey, CKK_RSA);
PORT_FreeArena(arena, PR_FALSE);
encrypted = g_new0(guchar, modlen);
temp = pkcs1pad2(password, modlen);
/* encrypt password, result will be in encrypted */
rv = PK11_PubEncryptRaw(pubKey, encrypted, temp, modlen, 0);
g_free(temp);
if (rv != SECSuccess)
{
purple_debug_error("steam", "password encrypt failed\n");
if (pubKey) SECKEY_DestroyPublicKey(pubKey);
g_free(encrypted);
return NULL;
}
output = purple_base64_encode(encrypted, modlen);
g_free(encrypted);
if (pubKey) SECKEY_DestroyPublicKey(pubKey);
return output;
}
#elif defined USE_GCRYPT_CRYPTO
#include
#include
// The following functions steam_util_str_hex2bytes, steam_crypt_rsa_enc and steam_encrypt_password
// (originally steam_crypt_rsa_enc_str) have been taken directly from steam-util.c and steam-crypt.c
// from the bitlbee-steam source code. The original files are released under the GNU General Public
// License version 2 and can be found at https://github.com/jgeboski/bitlbee-steam.
// All credit goes to the original author of bitlbee-steam, James Geboski .
GByteArray *
steam_util_str_hex2bytes(const gchar *str)
{
GByteArray *ret;
gboolean hax;
gsize size;
gchar val;
guint i;
guint d;
g_return_val_if_fail(str != NULL, NULL);
size = strlen(str);
hax = (size % 2) != 0;
ret = g_byte_array_new();
g_byte_array_set_size(ret, (size + 1) / 2);
memset(ret->data, 0, ret->len);
for (d = i = 0; i < size; i++, hax = !hax) {
val = g_ascii_xdigit_value(str[i]);
if (val < 0) {
g_byte_array_free(ret, TRUE);
return NULL;
}
if (hax)
ret->data[d++] |= val & 0x0F;
else
ret->data[d] |= (val << 4) & 0xF0;
}
return ret;
}
GByteArray *
steam_crypt_rsa_enc(const GByteArray *mod, const GByteArray *exp, const GByteArray *bytes)
{
GByteArray *ret;
gcry_mpi_t mmpi;
gcry_mpi_t empi;
gcry_mpi_t dmpi;
gcry_sexp_t kata;
gcry_sexp_t data;
gcry_sexp_t cata;
gcry_error_t res;
gsize size;
g_return_val_if_fail(mod != NULL, NULL);
g_return_val_if_fail(exp != NULL, NULL);
g_return_val_if_fail(bytes != NULL, NULL);
mmpi = empi = dmpi = NULL;
kata = data = cata = NULL;
ret = NULL;
res = gcry_mpi_scan(&mmpi, GCRYMPI_FMT_USG, mod->data, mod->len, NULL);
res |= gcry_mpi_scan(&empi, GCRYMPI_FMT_USG, exp->data, exp->len, NULL);
res |= gcry_mpi_scan(&dmpi, GCRYMPI_FMT_USG, bytes->data, bytes->len, NULL);
if (G_LIKELY(res == 0)) {
res = gcry_sexp_build(&kata, NULL, "(public-key(rsa(n %m)(e %m)))", mmpi, empi);
res |= gcry_sexp_build(&data, NULL, "(data(flags pkcs1)(value %m))", dmpi);
if (G_LIKELY(res == 0)) {
res = gcry_pk_encrypt(&cata, data, kata);
if (G_LIKELY(res == 0)) {
gcry_sexp_release(data);
data = gcry_sexp_find_token(cata, "a", 0);
if (G_LIKELY(data != NULL)) {
gcry_mpi_release(dmpi);
dmpi = gcry_sexp_nth_mpi(data, 1, GCRYMPI_FMT_USG);
if (G_LIKELY(dmpi != NULL)) {
ret = g_byte_array_new();
g_byte_array_set_size(ret, mod->len);
gcry_mpi_print(GCRYMPI_FMT_USG, ret->data, ret->len, &size, dmpi);
g_warn_if_fail(size <= mod->len);
g_byte_array_set_size(ret, size);
} else {
g_warn_if_reached();
}
} else {
g_warn_if_reached();
}
}
}
}
gcry_sexp_release(cata);
gcry_sexp_release(data);
gcry_sexp_release(kata);
gcry_mpi_release(dmpi);
gcry_mpi_release(empi);
gcry_mpi_release(mmpi);
return ret;
}
gchar *
steam_encrypt_password(const gchar *mod, const gchar *exp, const gchar *str)
{
GByteArray *bytes;
GByteArray *mytes;
GByteArray *eytes;
GByteArray *enc;
gchar *ret;
g_return_val_if_fail(mod != NULL, NULL);
g_return_val_if_fail(exp != NULL, NULL);
g_return_val_if_fail(str != NULL, NULL);
mytes = steam_util_str_hex2bytes(mod);
if (G_UNLIKELY(mytes == NULL))
return NULL;
eytes = steam_util_str_hex2bytes(exp);
if (G_UNLIKELY(eytes == NULL)) {
g_byte_array_free(mytes, TRUE);
return NULL;
}
bytes = g_byte_array_new();
g_byte_array_append(bytes, (guint8*) str, strlen(str));
enc = steam_crypt_rsa_enc(mytes, eytes, bytes);
g_byte_array_free(bytes, TRUE);
g_byte_array_free(eytes, TRUE);
g_byte_array_free(mytes, TRUE);
if (G_UNLIKELY(enc == NULL))
return NULL;
ret = g_base64_encode(enc->data, enc->len);
g_byte_array_free(enc, TRUE);
return ret;
}
#elif defined USE_MBEDTLS_CRYPTO
#include "mbedtls/config.h"
#include "mbedtls/rsa.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
gchar *
steam_encrypt_password(const gchar *modulus_str, const gchar *exponent_str, const gchar *password)
{
mbedtls_rsa_context rsa;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
int ret;
guchar *encrypted_password;
gchar *output;
// Init entropy context
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0);
if (ret != 0) {
purple_debug_error("steam", "failed to init entropy context, error=%d\n", ret);
return NULL;
}
// Init mbedtls rsa
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V15, 0);
// Read modulus
ret = mbedtls_mpi_read_string(&rsa.N, 16, modulus_str);
if (ret != 0) {
purple_debug_error("steam", "modulus parsing failed, error=%d\n", ret);
return NULL;
}
// Read exponent
ret = mbedtls_mpi_read_string(&rsa.E, 16, exponent_str);
if (ret != 0) {
purple_debug_error("steam", "exponent parsing failed, error=%d\n", ret);
return NULL;
}
// Set RSA key length
rsa.len = (mbedtls_mpi_bitlen(&rsa.N) + 7) >> 3;
// Allocate space for encrypted password
encrypted_password = g_new0(guchar, rsa.len);
ret = mbedtls_rsa_pkcs1_encrypt(&rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, strlen(password), (unsigned char*)password, encrypted_password);
if (ret != 0) {
purple_debug_error("steam", "password encryption failed, error=%d\n", ret);
g_free(encrypted_password);
return NULL;
}
output = purple_base64_encode(encrypted_password, (int)rsa.len);
g_free(encrypted_password);
return output;
}
#elif defined USE_WIN32_CRYPTO
#include
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#define SECURITY_WIN32
#include
gchar *
steam_encrypt_password(const gchar *modulus_str, const gchar *exponent_str, const gchar *password)
{
DWORD cchModulus = (DWORD)strlen(modulus_str);
int i;
BYTE *pbBuffer = 0;
BYTE *pKeyBlob = 0;
HCRYPTKEY phKey = 0;
HCRYPTPROV hCSP = 0;
// convert hex string to byte array
DWORD cbLen = 0, dwSkip = 0, dwFlags = 0;
if (!CryptStringToBinaryA(modulus_str, cchModulus, CRYPT_STRING_HEX, NULL, &cbLen, &dwSkip, &dwFlags))
{
purple_debug_error("steam", "password encryption failed, cant get length of modulus, error=%d\n", GetLastError());
return NULL;
}
// allocate a new buffer.
pbBuffer = (BYTE*)malloc(cbLen);
if (!CryptStringToBinaryA(modulus_str, cchModulus, CRYPT_STRING_HEX, pbBuffer, &cbLen, &dwSkip, &dwFlags))
{
purple_debug_error("steam", "password encryption failed, cant get modulus, error=%d\n", GetLastError());
free(pbBuffer);
return NULL;
}
// reverse byte array
for (i = 0; i < (int)(cbLen / 2); ++i)
{
BYTE temp = pbBuffer[cbLen - i - 1];
pbBuffer[cbLen - i - 1] = pbBuffer[i];
pbBuffer[i] = temp;
}
if (!CryptAcquireContext(&hCSP, NULL, NULL, PROV_RSA_AES, CRYPT_SILENT) &&
!CryptAcquireContext(&hCSP, NULL, NULL, PROV_RSA_AES, CRYPT_SILENT | CRYPT_NEWKEYSET))
{
purple_debug_error("steam", "password encryption failed, cant get a crypt context, error=%d\n", GetLastError());
free(pbBuffer);
return NULL;
}
// Move the key into the key container.
DWORD cbKeyBlob = sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY) + cbLen;
pKeyBlob = (BYTE*)malloc(cbKeyBlob);
// Fill in the data.
PUBLICKEYSTRUC *pPublicKey = (PUBLICKEYSTRUC*)pKeyBlob;
pPublicKey->bType = PUBLICKEYBLOB;
pPublicKey->bVersion = CUR_BLOB_VERSION; // Always use this value.
pPublicKey->reserved = 0; // Must be zero.
pPublicKey->aiKeyAlg = CALG_RSA_KEYX; // RSA public-key key exchange.
// The next block of data is the RSAPUBKEY structure.
RSAPUBKEY *pRsaPubKey = (RSAPUBKEY*)(pKeyBlob + sizeof(PUBLICKEYSTRUC));
pRsaPubKey->magic = 0x31415352; // RSA1 // Use public key
pRsaPubKey->bitlen = cbLen * 8; // Number of bits in the modulus.
//pRsaPubKey->pubexp = 0x10001; // "010001" // Exponent.
pRsaPubKey->pubexp = strtol(exponent_str, NULL, 16);
// Copy the modulus into the blob. Put the modulus directly after the
// RSAPUBKEY structure in the blob.
BYTE *pKey = (BYTE*)(((BYTE *)pRsaPubKey) + sizeof(RSAPUBKEY));
memcpy(pKey, pbBuffer, cbLen);
// Now import public key
if (!CryptImportKey(hCSP, pKeyBlob, cbKeyBlob, 0, 0, &phKey))
{
purple_debug_error("steam", "password encryption failed, couldnt create key, error=%d\n", GetLastError());
free(pKeyBlob);
free(pbBuffer);
CryptReleaseContext(hCSP, 0);
return NULL;
}
DWORD dataSize = strlen(password);
DWORD encryptedSize = dataSize;
// get length of encrypted data
if (!CryptEncrypt(phKey, 0, TRUE, 0, NULL, &encryptedSize, 0))
{
gint errorno = GetLastError();
purple_debug_error("steam", "password encryption failed, couldnt get length of RSA, error=%d %s\n", errorno, g_win32_error_message(errorno));
free(pKeyBlob);
free(pbBuffer);
CryptDestroyKey(phKey);
CryptReleaseContext(hCSP, 0);
return NULL;
}
BYTE *encryptedData = g_new0(BYTE, encryptedSize);
// encrypt password
memcpy(encryptedData, password, dataSize);
if (!CryptEncrypt(phKey, 0, TRUE, 0, encryptedData, &dataSize, encryptedSize))
{
purple_debug_error("steam", "password encryption failed, couldnt RSA the thing, error=%d\n", GetLastError());
free(pKeyBlob);
free(pbBuffer);
CryptDestroyKey(phKey);
CryptReleaseContext(hCSP, 0);
return NULL;
}
// reverse byte array again
for (i = 0; i < (int)(encryptedSize / 2); ++i)
{
BYTE temp = encryptedData[encryptedSize - i - 1];
encryptedData[encryptedSize - i - 1] = encryptedData[i];
encryptedData[i] = temp;
}
free(pKeyBlob);
CryptDestroyKey(phKey);
free(pbBuffer);
CryptReleaseContext(hCSP, 0);
gchar *ret = g_base64_encode(encryptedData, encryptedSize/2);
g_free(encryptedData);
return ret;
}
#elif defined USE_OPENSSL_CRYPTO
#include
#include
#include
#include
#include
#include
gchar *
steam_encrypt_password(const gchar *modulus_str, const gchar *exponent_str, const gchar *password)
{
BIGNUM *bn_modulus;
BIGNUM *bn_exponent;
RSA *rsa;
gchar *output = NULL;
guchar *encrypted;
int rv;
ERR_load_crypto_strings();
bn_modulus = BN_new();
rv = BN_hex2bn(&bn_modulus, modulus_str);
if (rv == 0)
{
purple_debug_error("steam", "modulus hext to bignum parse failed\n");
BN_free(bn_modulus);
return NULL;
}
bn_exponent = BN_new();
rv = BN_hex2bn(&bn_exponent, exponent_str);
if (rv == 0)
{
purple_debug_error("steam", "exponent hex to bignum parse failed\n");
BN_clear_free(bn_modulus);
BN_clear_free(bn_exponent);
return NULL;
}
rsa = RSA_new();
if (rsa == NULL)
{
purple_debug_error("steam", "RSA structure allocation failed\n");
BN_free(bn_modulus);
BN_free(bn_exponent);
return NULL;
}
BN_free(rsa->n);
rsa->n = bn_modulus;
BN_free(rsa->e);
rsa->e = bn_exponent;
encrypted = g_new0(guchar, RSA_size(rsa));
rv = RSA_public_encrypt((int)(strlen(password)),
(const unsigned char *)password,
encrypted,
rsa,
RSA_PKCS1_PADDING);
if (rv < 0)
{
unsigned long error_num = ERR_get_error();
char *error_str = ERR_error_string(error_num, NULL);
purple_debug_error("steam", "%s", error_str);
RSA_free(rsa);
g_free(encrypted);
return NULL;
}
output = purple_base64_encode(encrypted, RSA_size(rsa));
// Cleanup
RSA_free(rsa);
ERR_free_strings();
g_free(encrypted);
return output;
}
#endif