Server side verification of Google Play in-app billing purchase signature failed - node.js

i'm currently integrating Google Play in-app billing to my androidgame project, i have a Node.js server set up and plan to send it the "originalJson" and "signature" value of the Google Play purchase response for server side verification.
then i put up a bit of test on my Node.js server, first here are the "originalJson" and "signature" value of one of my purchase(fetched from the client side):
originalJson:{"orderId":"GPA.1312-8694-0319-25069","packageName":"com.shihu.sm.testin","productId":"com.shihu.sm.testin.diamond","purchaseTime":1452598011176,"purchaseState":0,"developerPayload":"{\"iabProductId\":\"com.shihu.sm.testin.diamond\",\"gOrderId\":\"2cb77de1a2a94db18b6df84f8037ea5b\",\"serverId\":\"6\",\"productId\":\"202\"}","purchaseToken":"bjoncdcebeclpklebmadidgb.AO-J1OyEbKLL0rhWQAc1hjdWyJPXHkAoHZTfZasqUuFWKWyAlnj-opiDLYILNRpnWgcblO8vV37fWf0kpeNMRZcgRT-fRxAO4P8VQPmU-TJakB-sCiRx8sUxL4nxnUBMnZdFWdpaIZDW5tP3Ck4aO57n1o66PwnjZw"}
signature:JdfwMxprv7iMbI5YnVIWXLEAqiDhAQQva2IdfxtmhqBvLNU4Msi8sj31mnrVJfShxNmQI3zhlNUrCCaXdraFM0/y8O4PoZWYr+PFjCmlMovhG+ldeImEu7x52GLoQ7DsO8Yh4aLYmxemezFc1RjcSpq+l6Zzu9T6j3fHjLfQ060SEFapZITI/poxlFyvJX3bHhF9wGP54tL6pGjB/7fBEqTM1zHXUYeZyz+4akqV8oODlIWwMKhvN5tX/Zra9kh9hm0bnJT/1YWso3tLlT/WTK9nsP1l/lTnEXvgzq9QVSGbT/cpD7KSbR5N4i/NmPYAlCOvesW9OlRD05L8yytpBw==
then i wrote the following code to do the verification with "RSA-SHA1" algorithm and "base64" signature encoding:
var crypto = require('crypto');
console.log('start verification');
var public_key = "-----BEGIN PUBLIC KEY-----" + "\r\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg+VmzvTvb856ur/J+PWC" + "\r\n" +
"gFRhLYV/chAuWzUuqlIh5gnYz1RFOYymCWAKP3wguol8YSe/72zEqAvPutBU2XVj" + "\r\n" +
"zx3sHT+GUInbKjgZHzxw0viPh//OfaooEvEFMz9C6J8ABwpGNQUpACmyw12ZKshP" + "\r\n" +
"HCJ6PZV+nsWry6PEZgnYCF7w5SDP4GY2tr3Q5D0iQwoALA40KYQfsKZ6pI5L8bDT" + "\r\n" +
"2MLTFoemg/npeARy9HYkbonPatBhWjp2flzBRcyQx7DyQ7csLvPl5AGHRT4h5RBq" + "\r\n" +
"RlLj+DBgNDAdwvHGyfhbTz7fPsT6xn7qifxAN+2gQsemSVmhi15zECF/k5MtTiOF" + "\r\n" +
"owIDAQAB" + "\r\n" +
"-----END PUBLIC KEY-----";
verifier= crypto.createVerify("RSA-SHA1");
originalJson = '{"orderId":"GPA.1312-8694-0319-25069","packageName":"com.shihu.sm.testin","productId":"com.shihu.sm.testin.diamond","purchaseTime":1452598011176,"purchaseState":0,"developerPayload":"{\"iabProductId\":\"com.shihu.sm.testin.diamond\",\"gOrderId\":\"2cb77de1a2a94db18b6df84f8037ea5b\",\"serverId\":\"6\",\"productId\":\"202\"}","purchaseToken":"bjoncdcebeclpklebmadidgb.AO-J1OyEbKLL0rhWQAc1hjdWyJPXHkAoHZTfZasqUuFWKWyAlnj-opiDLYILNRpnWgcblO8vV37fWf0kpeNMRZcgRT-fRxAO4P8VQPmU-TJakB-sCiRx8sUxL4nxnUBMnZdFWdpaIZDW5tP3Ck4aO57n1o66PwnjZw"}';
signature = 'JdfwMxprv7iMbI5YnVIWXLEAqiDhAQQva2IdfxtmhqBvLNU4Msi8sj31mnrVJfShxNmQI3zhlNUrCCaXdraFM0/y8O4PoZWYr+PFjCmlMovhG+ldeImEu7x52GLoQ7DsO8Yh4aLYmxemezFc1RjcSpq+l6Zzu9T6j3fHjLfQ060SEFapZITI/poxlFyvJX3bHhF9wGP54tL6pGjB/7fBEqTM1zHXUYeZyz+4akqV8oODlIWwMKhvN5tX/Zra9kh9hm0bnJT/1YWso3tLlT/WTK9nsP1l/lTnEXvgzq9QVSGbT/cpD7KSbR5N4i/NmPYAlCOvesW9OlRD05L8yytpBw=='
verifier.update(originalJson);
if(verifier.verify(public_key, signature, "base64"))
console.log('verification succeeded');
else
console.log("verification failed");
the key string in the middle is the base64 encoded public key from Google Console split by '\r\n' with every 64 characters. at the beginning i didn't split it into chunks of 64 characters and kept failing with error saying can't generate the pub key object, it was later i followed some examples on the internet and got passed that, but till now, i haven't got a successful verification result yet.
i have referenced some more examples, and i think the 'RSA-SHA1' and 'base64' settings for the verification are the correct ones, so what am i still missing or doing wrong?
thanks

It seems that your originalJson string is missing some necessary escaping.
I've managed to verify the signature with the escaping added back in:
var originalJson = '{"orderId":"GPA.1312-8694-0319-25069","packageName":"com.shihu.sm.testin","productId":"com.shihu.sm.testin.diamond","purchaseTime":1452598011176,"purchaseState":0,"developerPayload":"{\\"iabProductId\\":\\"com.shihu.sm.testin.diamond\\",\\"gOrderId\\":\\"2cb77de1a2a94db18b6df84f8037ea5b\\",\\"serverId\\":\\"6\\",\\"productId\\":\\"202\\"}","purchaseToken":"bjoncdcebeclpklebmadidgb.AO-J1OyEbKLL0rhWQAc1hjdWyJPXHkAoHZTfZasqUuFWKWyAlnj-opiDLYILNRpnWgcblO8vV37fWf0kpeNMRZcgRT-fRxAO4P8VQPmU-TJakB-sCiRx8sUxL4nxnUBMnZdFWdpaIZDW5tP3Ck4aO57n1o66PwnjZw"}';
Pay attention to the \\'s. The string is different otherwise.

Related

eBay - Digital Signature in SAP PI UDF - Signature validation failed error

`I'm getting a { "errors": [ { "errorId": 215120, "domain": "ACCESS", "category": "REQUEST", "message": "Signature validation failed", "longMessage": "Signature validation failed to fulfill the request." } ]} error when i try to test within our mapping via Java UDF.
I'm using RSA as the cipher. This is what the code looks like.
//signature base - signature base will be converted to bytes (UTF-8) then to base64. will be used in signature header.
signatureInput.append("\"x-ebay-signature-key\":");
signatureInput.append(" " + jwe);
signatureInput.append("\n");
signatureInput.append(" \"#method\":");
signatureInput.append(" GET");
signatureInput.append("\n");
signatureInput.append(" \"#path\":");
signatureInput.append(" /sell/finances/v1/payout ");
signatureInput.append("\n");
signatureInput.append("\"#authority\":");
signatureInput.append(" apiz.ebay.com");
signatureInput.append("\n");
signatureInput.append("\"#signature-params\": (\"x-ebay-signature-key\" \"#method\" \"#path\" \"#authority\")" );
signatureInput.append(";created=" + unixTime);
//signature-input - will be used in signature-input header
signatureParams.append("\"#signature-params\": (\"x-ebay-signature-key\" \"#method\" \"#path\" \"#authority\")" );
signatureParams.append(";created=" + unixTime);
**private key will be converted to bytes (UTF-8) then to base64. Then extract the private key
**
byte[] privateKeyBytes = Base64.getDecoder().decode(EncodedPrivateKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privKey = kf.generatePrivate(keySpec);
signBase will be signed using the private key here
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privKey);
signature.update(EncodedSignBase.getBytes());
byte[] signedData = signature.sign();
signed message will be encoded to base64
String signedMessage = Base64.getEncoder().encodeToString(signedData);
these are the request headers
con_payouts.setRequestMethod("GET");
con_payouts.setRequestProperty("Content-Type", "application/json");
con_payouts.setRequestProperty("Authorization", "Bearer " + accessToken);
con_payouts.setRequestProperty("Accept", "application/json");
con_payouts.setRequestProperty("Accept-Charset", "utf-8");
con_payouts.setRequestProperty("Accept-Encoding", "application/gzip");
con_payouts.setRequestProperty("Content-Type", "application/json");
con_payouts.setRequestProperty("X-EBAY-C-MARKETPLACE-ID", "EBAY_XX *- country code*");
con_payouts.setRequestProperty("x-ebay-signature-key", jwe);
con_payouts.setRequestProperty("Signature", "sig1=:" + signedMessage + ":");
con_payouts.setRequestProperty("Signature-Input", "sig1=" + signParams);
con_payouts.setRequestProperty("x-ebay-enforce-signature", "true");
i referenced this code mostly from this example, -code.com/Java/ebay_add_digital_signature_to_http_request.asp. But i'm still getting the same error. At this point i'm not sure where's the issue in the code.
your text
I already tried interchanging some of the variables, i also tried enconding everything to base64 before the request. Also asked help from ebay support, they provided a sample valid signature, then have to check if i will get the same output, but i'm still getting the same error. I also encountered the "invalid timestamp", but as per ebay, timestamp should still be applicable within 15 minutes.`

How do I Get the Token I Need to for my Script to hit my Azure Stored Procedure?

I have an azure stored procedure, and I need to hit it with a python script that I'm going to upload as a webjob to schedule it to run once per day.
I've been reading the docs on executing a stored procedure, the common request headers for Azure Cosmos DB rest calls, and the page on access control, but the access control page mentions that these keys are for read queries only (so I assume not for hitting stored procedures, which have rights to do any sort of query or else that seems like a huge vulnerability hole).
I need to know specifically how do I get a key from Azure in python to hit my stored procedure endpoint?
Update 1
I was able, finally, to construct the Authorization string and send it, along with some other headers, to the server. But I am still getting an unauthorized response.
The response:
{
"code": "Unauthorized",
"message": "The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'post\nsprocs\ndbs/metrics/colls/LoungeVisits/sprocs/calculateAverage\nfri, 05 oct 2018 19:06:17 gmt\n\n'\r\nActivityId: 41cd36af-ad0e-40c3-84c8-761ebd14bf6d, Microsoft.Azure.Documents.Common/2.1.0.0"
}
The request headers:
{
Authorization: [my-auth-string],
x-ms-version: "2017-02-22", //My DB was created after this, the latest version, so I assume it uses this version; can I verify this somehow?
x-ms-date: "Fri, 05 Oct 2018 19:06:17 GMT", // My js for returning the auth string also returns the date, so I copy both in
Content-Type: application/json
}
Code to generate auth string which is then copy/pasted into Postman:
var crypto = require("crypto");
var inputKey = "my-key-from-azure";
var today = new Date().toUTCString();
console.log(today);
console.log(getAuthorizationTokenUsingMasterKey("POST", "dbs", "dbs/ToDoList", today, inputKey))
function getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceId, date, masterKey)
{
var key = new Buffer(masterKey, "base64");
var text = (verb || "").toLowerCase() + "\n" +
(resourceType || "").toLowerCase() + "\n" +
(resourceId || "") + "\n" +
date.toLowerCase() + "\n" +
"" + "\n";
var body = new Buffer(text, "utf8");
var signature = crypto.createHmac("sha256", key).update(body).digest("base64");
var MasterToken = "master";
var TokenVersion = "1.0";
return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
}
The page about authorization headers is for any Cosmos DB REST request: query, stored procedures, etc.
Azure cosmos DB has python SDK which is the recommended and supported way for such scenarios.
Also python SDK code is open-sourced. Here is the reference to auth header creation code enter link description here

Hue API remote Basic Authentication

I'm having issues using the Basic Authentication method for the Hue remote API.
When POSTing to https://api.meethue.com/oauth2/token?code={code}&grant_type=authorization_code with the built Authorization-header I get this response:
{
"fault": {
"faultstring": "Invalid client identifier {0}",
"detail": {
"errorcode": "oauth.v2.InvalidClientIdentifier"
}
}
}
I assume then that I am building the token in the wrong way, but the docs (see Basic Authentication) is a bit vague on what to actually do.
The docs says that I should send a header via this format: Authorization: Basic <base64(clientid:clientsecret)> and that it should be encoded in base-64:
you would need to send a Basic authorization header that includes a base64 encrypted hash of your clientid and clientsecret.
And from the Digest-method, I assume MD5 is used and then digested to base-64.
Here's what I've tried, all with the same error-code:
'Basic ' + crypto.createHash('md5').update(clientId + clientSecret).digest('base64')
'Basic ' + crypto.createHash('md5').update(clientId + ':' + clientSecret).digest('base64')
'Basic ' + (clientId + ':' + clientSecret).toString('base64')
'Basic ' + (clientId + clientSecret).toString('base64')
What more is there to try?
#Tokfrans
you can create a test token with clientid:secret by using the site
https://www.base64encode.org/
it will give you a valid token that you can use with Basic authentication
keep in mind that you first need to get a code which you can then use to get a accesstoken
https://api.meethue.com/oauth2/auth?clientid=xxxxxx&appid=xxxxx&deviceid=xxxx&devicename=xxxx&state=xxxx&response_type=code

A way to validate session cookies without storing them

Basic info
I have a webserver with all that fancy stuff.
It runs on my half-potato PC, so I don't have any free 64TB of SSD storage.
I have to care about scalability, because one day I might have a 1000 users.
Wow, such users, very a lot!
I don't want any passwords to be stolen if user will lose his device. So I decided to use sessions.
I don't want to store all of the user sessions, because that can be possibly a 100 of devices. So 1000 * 100 is a lot of session cookies.
I thought about a way not to store, but to validate sessions to check if request is from the owner of the account.
Structure
Global
// Secret 128 bytes array.
const ByteArray serverMagic;
// Generates a SHA512 hash based on data.
function hash(ByteArray data);
// Encodes array of bytes into base64 string.
function base64(ByteArray data);
// Returns current time on the server.
function getCurrentDate();
// Returns userMagic based on the user ID.
function getUserMagic(Integer uid);
// Returns true if user entered wrong password.
function isLoginInvalid(Integer uid, String password);
function Object.toShortString(); // Encodes an object into a short ASCII string.
For each user
const Integer uid; // User ID.
var String password; // Secret code that gives you all of that damn power.
var ByteArray userMagic // Secret 128 bytes array.
Logic
If user wants to log in
he will send:
Session expiration date (so cookie won't be valid after a desired amount of time)
User ID
Password (or hash of the password, it doesn't really matter)
function logIn(Date expire, Integer uid, String password) {
if(isLoginInvalid(uid, password)) then
return error;
String encExpire = expire.toShortString();
String encUID = uid.toShortString();
ByteArray userMagic = getUserMagic(uid);
ByteArray food = encExpire + '_' + serverMagic + userMagic + '_' + encUID;
ByteArray hash = hash(food);
String session = encExpire + '_' + encUID + '_' + base64(hash);
return session;
}
Client will save that session cookie.
If user will make some action
he will send a session cookie to the server.
// After parsing session cookie
function request(Date sessionExpire, Integer uid, ByteArray sessionHash) {
if(sessionExpire < getCurrentDate()) then
return error;
String encExpire = sessionExpire.toShortString();
String encUID = uid.toShortString();
ByteArray userMagic = getUserMagic(uid);
ByteArray food = encExpire + '_' + serverMagic + userMagic + '_' + encUID;
ByteArray hash = hash(food);
if(hash == sessionHash) then
return success;
else
return error;
}
If user will want to invalidate all sessions
due to device loss or password change server can just generate a new userMagic, that will make all of the previous session cookies invalid.
Questions
Is it a good way to store not to store sessions?
Is there a flaw in my system?
Can it be cracked?
And how can it be cracked?
How can it be abused?
How can I improve my system?
Revised answer:
The problem with solutions like this is that they are often vulnerable to very subtle bugs, such as length extension attacks. Play it safe and use HMAC instead.
FYI .Net has some construction that is not too different than this, but uses HMAC and no user magic.
Previous, retracted answer:
Any user can compute:
String session = encExpire + '_' + encUID + '_' + base64(hash);
Which means any user can create arbitrary sessions. The hacker is going to do this with encUID set to the administrator uid, which may 1 or 0 or something like that.
I could potentially suggest fixing this bug using HMAC with a secret key, but you really should not be implementing this yourself. Find a widely used and well studied library that does session management for you rather than trying to re-invent the wheel.

node.js digital signature does not work

I am trying to generate a digital signature using the crypto module for node.js (0.6.15). The following code prints nothing (on both a windows and a linux machine) and res is of length 0. Also signer never throws an exception no matter what dummy input I give as key. openssl is installed in version 1.0.1. What am I doing wrong?
var crypto = require('crypto');
var signer = crypto.createSign("RSA-SHA1")
signer.update("sign me!")
//dummy key
var private_key = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj" +
"7RAGxefGRATiq4mXtzc9Q91W7uT0DTaFEbjzVch9aGsNjmLs4QHsoZbuoUmi0st4" +
"x5z9SQpTAKC/dW8muzacT3E7dJJYh03MAO6RiH4LG34VRTq1SQN6qDt2rCK85eG4" +
"5NHI4jceptZNu6Zot1zyO5/PYuFpAgMBAAECgYAhspeyF3M/xB7WIixy1oBiXMLY" +
"isESFAumgfhwU2LotkVRD6rgNl1QtMe3kCNWa9pCWQcYkxeI0IzA+JmFu2shVvoR" +
"oL7eV4VCe1Af33z24E46+cY5grxNhHt/LyCnZKcitvCcrzXExUc5n6KngX0mMKgk" +
"W7skZDwsnKzhyUV8wQJBAN2bQMeASQVOqdfqBdFgC/NPnKY2cuDi6h659QN1l+kg" +
"X3ywdZ7KKftJo1G9l45SN9YpkyEd9zEO6PMFaufJvZUCQQDbtAWxk0i8BT3UTNWC" +
"T/9bUQROPcGZagwwnRFByX7gpmfkf1ImIvbWVXSpX68/IjbjSkTw1nj/Yj1NwFZ0" +
"nxeFAkEAzPhRpXVBlPgaXkvlz7AfvY+wW4hXHyyi0YK8XdPBi25XA5SPZiylQfjt" +
"Z6iN6qSfYqYXoPT/c0/QJR+orvVJNQJBANhRPNXljVTK2GDCseoXd/ZiI5ohxg+W" +
"UaA/1fDvQsRQM7TQA4NXI7BO/YmSk4rW1jIeOxjiIspY4MFAIh+7UL0CQFL6zTg6" +
"wfeMlEZzvgqwCGoLuvTnqtvyg45z7pfcrg2cHdgCXIy9kErcjwGiu6BOevEA1qTW" +
"Rk+bv0tknWvcz/s="
var res = signer.sign(private_key, output_format='base64')
console.log(res);
As Ben Noordhuis tells me here the key used above is not in the correct format. it is best to load the pem format from disk.

Resources