How to authenticate Game Center User from 3rd party node.js server - ios

I've been trying to get the new iOS Game Center GKPlayer method, generateIdentityVerificationSignatureWithCompletionHandler, working so we can securely rely on the Game Center credentials for authentication. We're using Node.js as the backend server, and I've been trying to verify the signature but to no avail.
Here is the code on the server side that I have - if there's anyone who can chime in on what's missing, that'd be appreciated. The question has been answered somewhat here: How to authenticate the GKLocalPlayer on my 'third party server'?, but Node.js hasn't specifically been tackled. Note that the code below doesn't ensures the validity of the certificate with a signing authority (yet).
//Client sends the payload below
//json.playerId - UTF-8 string
//json.bundleId - UTF-8 string
//json.timestamp - Hex string
//json.salt - base64 encoded
//json.publicKeyURL - UTF-8 string
//json.signature - base64 encoded
var json = JSON.parse(req.body);
console.log(JSON.stringify(json));
//get the certificate
getCertificate(json.publicKeyURL, function(cert){
//read file from fs for now, since getCertificate returns cert in DER format
fs = require('fs');
fs.readFile('/gc-sb.pem', 'utf8', function (err,data) {
if (err) {
console.log(err);
} else {
console.log(data);
var verifier = crypto.createVerify("sha1WithRSAEncryption");
verifier.write(json.playerId, "utf8");
verifier.write(json.bundleId, "utf8");
verifier.write(json.hexTimestamp, "hex");
verifier.write(json.salt, "base64");
var isValid = verifier.verify(data, json.signature, "base64");
console.log("isvalid: " + isValid);
}
});
});
One thing I've found using the crypto module in node.js is that it seems to want the certificate in PEM format, and I believe the format retrieved from Apple is DER. Until I figure out how to convert the DER file to PEM, I've temporarily converted it using
openssl x509 -in gc-sb.cer -inform der -outform pem -out gc-sb.pem
The main thing for me is being able to validate the signature first. Conversion of the certificate and verifying it against a signing authority will come later :)
EDIT: I've figured it out - I was hashing the playerId, bundleId, timestamp and salt, and then using the hashed value as information to verify. I needed to just put those pieces of information into the verifier to verify without the SHA-1 hash (since the verifier will be taking care of it). I've modified the code above to "make it work". Hope this helps anyone that comes across this.

Here is how you can validate gamecenter identity using nodejs. It convert also the der certificate format to pem on the fly.
var crypto = require('crypto');
var request = require('request');
var ref = require('ref');
var token = require('./test.json');
request({url: token.publicKeyURL, encoding: null}, function (error, response, body) {
if (!error && response.statusCode == 200) {
var verifier = crypto.createVerify("sha1");
verifier.update(token.playerId, "utf8");
verifier.update(token.bundleId, "utf8");
var buf = ref.alloc('uint64');
ref.writeUInt64BE(buf, 0, token.timestamp.toString());
verifier.update(buf);
verifier.update(token.salt, 'base64');
var pmd = '-----BEGIN CERTIFICATE-----';
var base64 = body.toString('base64');
var size = base64.length;
for (var i = 0; i < size; i = i + 64) {
var end = i + 64 < size ? i + 64 : size;
pmd = pmd + '\n' + base64.substring(i, end);
}
pmd = pmd + '\n-----END CERTIFICATE-----';
var valid = verifier.verify(pmd, token.signature, "base64");
console.log(valid);
}
});

It's seems there's a npm package for it.
https://github.com/maeltm/node-gamecenter-identity-verifier

Related

How create a Signed URL to upload Files in Google Cloud Storage using Dart

I'm trying to create an API in Dart to generate a Signed URL to upload a file in Cloud Storage but even following the google documentation I can't successfully generate. In all my attempts, when making the request the API returns me: The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
I think the error is in generating the signature, or in the hash of the canonical request
final canonicalRequest = getCanonicalRequest(
canonicalUri,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
);
final bytes = utf8.encode(canonicalRequest);
final hashedCanonicalRequest = HEX.encode(sha256.convert(bytes).bytes);
final stringToSign = getStringToSign(
dateTimestamp,
credentialScope,
hashedCanonicalRequest,
);
final signature = await getSignature(stringToSign);
Future<String> getSignature(String stringToSign) async {
final privateKey =
await parseKeyFromFile<pointy.RSAPrivateKey>('private.pem');
final signer =
Signer(RSASigner(RSASignDigest.SHA256, privateKey: privateKey));
final signature = signer.sign(stringToSign);
return HEX.encode(signature.bytes);
}

"Export" x509 certificate in Ruby

I'm communicating with an API that has the following directions:
Install the issued x509 certificate onto the client server.
Export the x509 certificate using the supplied password and default Machine Key Set into memory.
Base64 encode the exported bytes of the x509 certificate.
Add ‘X509Certificate’ as an HTTP header and set its value to the result of step 3.
Step 1 and 4 are easy, but I have no idea on 2 or or the 'export' portion of 3. I have tried Googling for some time and I'm not sure exactly where to even really start.
Would someone point me in the right direction on how to "export" a certificate with "machine key set"?
Here is what I have so far
raw_data = File.read('cert.pfx')
pkcs = OpenSSL::PKCS12.new(raw_data, 'password')
cert = OpenSSL::X509::Certificate.new(pkcs.certificate.to_pem)
Here is equivalent .NET code:
public string GetBase64Cert(string certificateThumbprint)
{
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var foundCertificates = store.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, false);
if (foundCertificates.Count != 1)
{
return null;
}
var certByteArray = foundCertificates[0].Export(X509ContentType.Cert);
store.Close();
return Convert.ToBase64String(certByteArray);
}
}
And equivalent PHP code:
public function setx509($x509file) {
$cert = openssl_x509_parse($x509file);
$base64cert = base64_encode($cert);
return $base64cert;
}
Try
pkcs = OpenSSL::PKCS12.new(File.read('cert.pfx'), 'password')
str = Base64.urlsafe_encode64(pkcs.certificate.to_der)
Probably also str.gsub(/=+$/, '') to cut off padding

How do I use Bouncy Castle to read an App Store In App Purchase receipt? (PKCS7)

I have a receipt in PKCS7 that I obtained from my iOS app. Apple says this is a PKCS7 structure, and within that, is information regarding past recurring purchases.
I have the raw receipt here, encoded in Base64.
I've sent this payload, with my secret key, to Apple and got this response. Based on WWDC videos, and documentation, I believe I should be able to read this receipt directly, and without sending it to apple.
I'm guessing that PEMReader in BC is the correct starting point parse it, however I'm not sure how to actually use it. I've scanned the BC source code for the strings "PKCS", and looked at unit tests, however all I ever see are casts from PEMReader into another format.
using (var stream1 = new MemoryStream(receipt.Data))
using (var stream2 = new StreamReader(stream1))
{
var pp = new PemReader(stream2);
pp.ReadObject();
}
Question
How do I use Bouncy Castle to verify a raw receipt payload generated from Apple Store?
Note to self: I intend to use this to inspect the actual binary to see if ApplicationUsername is included in the receipt, yet for some reason isn't returned in the JSON result when posting the server. (Bug on Apple's side?)
I've made this using Java 7 and BouncyCastle 1.56.
For the code below, consider that pemString is the PEM string you provided. But I had to make some modifications:
format (break lines for every 64 characters) - I've made a small program to do that
include BEGIN and END headers
So my PEM looks like:
-----BEGIN PKCS7-----
MIIv5gYJKoZIhvcNAQcCoIIv1zCCL9MCAQExCzAJBgUrDgMCGgUAMIIfhwYJKoZI
hvcNAQcBoIIfeASCH3Qxgh9wMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC
AQEEAwIBADALAgELAgEBBAMCAQAwCwIBDwIBAQQDAgEAMAsCARACAQEEAwIBADAL
....
gdTu2uzkTyT+vcBlaLHK1ZpjKozsBds7ys6Q4EFp7OLxtJTj7saEDYXCNQtXBjwl
UfSGvQkXeIbsaqSPvOVIE83K3ki5i64gccA=
-----END PKCS7-----
For the code below, I followed the definition in Apple's doc:
ReceiptAttribute ::= SEQUENCE {
type INTEGER,
version INTEGER,
value OCTET STRING
}
Payload ::= SET OF ReceiptAttribute
Code:
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.DLSet;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
String pemString = // PEM String as described above
PemReader reader = new PemReader(new StringReader(pemString));
PemObject pemObject = reader.readPemObject();
reader.close();
CMSSignedData s = new CMSSignedData(pemObject.getContent());
byte[] content = (byte[]) s.getSignedContent().getContent();
ASN1InputStream in = new ASN1InputStream(content);
// Payload: a SET of ReceiptAttribute
DLSet set = (DLSet) DLSet.fromByteArray(in.readObject().getEncoded());
int size = set.size();
for (int i = 0; i < size; i++) {
// ReceiptAttribute is a SEQUENCE
DLSequence seq = (DLSequence) set.getObjectAt(i);
// value is the third element of the sequence
DEROctetString oct = (DEROctetString) seq.getObjectAt(2);
ASN1Object obj = readObject(oct.getOctets()); // *** see comments below ***
}
in.close();
// readObject method
public ASN1Object readObject(byte[] b) throws IOException {
ASN1InputStream in = null;
try {
in = new ASN1InputStream(b);
return in.readObject();
} catch (Exception e) {
// if error occurs, just return the octet string
return new DEROctetString(b);
} finally {
in.close();
}
}
Variable obj will be the content of the ReceiptAttribute, and it can vary a lot - I've seen DERIA5String, DERUTF8String, ASN1Integer and many others. As I don't know all possible values of this field, I think it's up to you to check each value.

Having Trouble Encrypting on iOS and decrypting on Node.js using RAW RSA

I am trying to encrypt something on the iOS side and decrypt it on my node.js server. On the server, I am using the library forge. I was able to encrypt something and decrypt it all on node.js, and that worked. I encrypted like this: const encryptedPassword = publicKey.encrypt(password, 'RAW'); and decrypted like this: const password = privateKey.decrypt(encryptedPassword, 'RAW');.
Now, instead of encrypting in the server, I would like to encrypt on my iOS app, but still decrypt using the same way. I found this library, swift-rsautils. https://github.com/btnguyen2k/swift-rsautils/blob/master/Swift-RSAUtils/RSAUtils.swift It has this function called encryptWithRSAKey, which is what I am using. Since it is raw encryption, I tried to pass in padding SecPaddingNone. However, unfortunately it doesn't work and I am unable to decrypt on the the server. The error message is invalid length, and the length of the base64 data does seem a lot bigger. Does anyone know how I can fix this problem?
Here is my iOS code:
let dataString = text.dataUsingEncoding(NSUTF8StringEncoding)
let certificateLabel = "certificate"
let certificateRef = self.getCertificateFromKeyChain(certificateLabel)
let certificateData = self.getDataFromCertificate(certificateRef)
let cryptoImportExportManager = CryptoExportImportManager()
let publicKeyRef = cryptoImportExportManager.importPublicKeyReferenceFromDERCertificate(certificateData)
let encryptedData = self.encryptWithRSAKey(data, rsaKeyRef: publicKeyRef!, padding: SecPadding.None)
let base64EncryptedString = encryptedData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
I am then sending this base64 encrypted string to the server and trying to decrypt using the private key. It doesn't work unfortunately.
This isn't the answer to your exact question, since I haven't used that specific library, but I have played a little with encryption in javascript and node.js.
I was able to implement the eccjs library which is the Stanford Javascript Crypto Library (SJCL) built with asymmetric elliptical curve support.
On the node.js side:
var ecc = require('eccjs');
var cryptoKeys = ecc.generate(ecc.ENC_DEC); //crypto_keys.enc is the pubic key for encoding. crypto_keys.dec is the private key for decoding.
//send the public key to the client
app.get('/PublicKey', function(req, res){
res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
res.setHeader('Expires', '-1');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Content-type', 'text/plain');
res.send('var publicKey = ' + JSON.stringify(cryptoKeys.enc) + ';');
});
//authenticate a user name and a password (encrypted by client) against the domain controller
app.get('/Authenticate', function(req, res){
res.setHeader('Content-type', 'text/plain');
var url = "ldap://na-us-dc01.am.corp.airliquide.com";
var userPrincipalName = req.query.username + "#US-AIRLIQUIDE";
try
{
var cipherMessage = JSON.parse(req.query.encryptedPassword);
var password = ecc.decrypt(cryptoKeys.dec, cipherMessage);
//... Authentication goes here ...
}
catch(err)
{
console.log("Error with authentication: ",err);
res.send("Error with authentication: " + JSON.stringify(err,null,' '));
}
});
In the client:
<script src="ecc.js"></script>
<script src="../PublicKey"></script> <!-- This returns the variable publicKey which has been set equal to the server's public key -->
<script>
function login() {
var plainTextPassword = document.getElementById('password').value;
var cipherTextPassword = ecc.encrypt(publicKey, plainTextPassword);
var username = document.getElementById('name').value;
console.log(ecc, publicKey, cipherTextPassword);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = (function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
document.getElementById('result').innerHTML = xhttp.responseText;
console.log("Response: " + xhttp.responseText);
}
}).bind(this);
xhttp.open("GET", "../Authenticate?username=" + username + "&encryptedPassword=" + JSON.stringify(cipherTextPassword), true);
xhttp.send();
}
</script>
I'm sure this solution is not perfectly secure and I ended up not using it, and instead implemented HTTPS. However this should provide you with the necessary pieces to do your own asymmetric encryption if that's your ultimate goal.
SecPadding.None has been removed from Swift3 and the code of Swift-RSAUtils has changed so I cannot reproduce your problem.
However I am able to encrypt and then decrypt data with the following code:
let data = "Data to be encrypted".data(using: String.Encoding.utf8)!
let e = RSAUtils.encryptWithRSAKey(data, rsaKeyRef: publicSecKeyRef, padding: SecPadding())
let d = try! RSAUtils.decryptWithRSAPrivateKey(encryptedData: e!, privkeyBase64: privkey)
Can you try again with latest version of Swift-RSAUtils at https://github.com/btnguyen2k/swiftutils ?
Edit: I noticed that you got error "invalid message length". Be noted that RSA cannot encrypt a very large amount of data in one go. It can encrypt a message up to key's size - 11 length.
To workaround that limitation, Swift-RSAUtils splits the long data into small chunks, encrypts each chunk and merges them all together. So, at server side, you should do similarly: split the encrypted data into chunks of key's size, decrypts each one and merges them to the final result.

Apple Push Notifications Using Moon-APNS or APNS-Sharp

I am having really hard time figuring out how to send messages from my server to the APNS. I have used Moon-APNS and APNS-Sharp and I am stuck at the same error which is "parameter is incorrect". I generated the p12 file using KeyChain. I dragged the file into my Win 7 virtual environment and put it inside the bin\debug folder. Here is the code for Moon-APNS:
static void Main(string[] args)
{
var deviceToken = "21212d6fefebde4d317cab41afff65631b5a4d47e5d85da305ec610b4013e616";
var payload = new NotificationPayload(deviceToken, "hello world");
var notificationList = new List<NotificationPayload>() { payload };
var push = new PushNotification(true, "PushNotificationTest.p12", "pushchat");
var result = push.SendToApple(notificationList);
Console.WriteLine("Hello World");
}
Anyone has ideas?
I think this will help you :
OSX Keychain
After you've created the appropriate Push Notification Certificate in iPhone Developer Program Portal you should have downloaded a file named something like apn_developer_identity.cer. If you have not done so already, you should open/import this file into Keychain, into your login section.
Finally, if you filter Keychain to show your login container's Certificates, you should see your Certificate listed. Expand the certificate, and there should be a Key underneath/attached to it.
Right Click or Ctrl+Click on the appropriate Certificate and choose Export. Keychain will ask you to choose a password to export to. Pick one and remember it. You should end up with a .p12 file. You will need this file and the password you picked to use the Notification and Feedback Libraries here.
OpenSSL
Here is how to create a PKCS12 format file using open ssl, you will need your developer private key (which can be exported from the keychain) and the CertificateSigningRequest??.certSigningRequest
1. Convert apn_developer_identity.cer (der format) to pem:
openssl x509 -in apn_developer_identity.cer -inform DER -out apn_developer_identity.pem -outform PEM}
2. Next, Convert p12 private key to pem (requires the input of a minimum 4 char password):
openssl pkcs12 -nocerts -out private_dev_key.pem -in private_dev_key.p12
3. (Optional): If you want to remove password from the private key:
openssl rsa -out private_key_noenc.pem -in private_key.pem
4. Take the certificate and the key (with or without password) and create a PKCS#12 format file:
openssl pkcs12 -export -in apn_developer_identity.pem -inkey private_key_noenc.pem -certfile CertificateSigningRequest??.certSigningRequest -name "apn_developer_identity" -out apn_developer_identity.p12
I found Moon-APNS easier to use and configure on my app.
Solved the problem by following the link:
http://code.google.com/p/apns-sharp/wiki/HowToCreatePKCS12Certificate
and then generating the .p12 file using the openssl approach.
Have you tried using APNS-Sharp example project to send notifications? I am using similar code, it works just fine...this is what one of my methods looks like
public void SendToSome(List<string> tokens)
{
//Variables you may need to edit:
//---------------------------------
bool sandbox = true;
//Put your device token in here
//Put your PKCS12 .p12 or .pfx filename here.
// Assumes it is in the same directory as your app
string p12File = "Certificates.p12";
//This is the password that you protected your p12File
// If you did not use a password, set it as null or an empty string
string p12FilePassword = "";
//Number of milliseconds to wait in between sending notifications in the loop
// This is just to demonstrate that the APNS connection stays alive between messages
// int sleepBetweenNotifications = 15000;
//Actual Code starts below:
//--------------------------------
string p12Filename = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, p12File);
NotificationService service = new NotificationService(sandbox, p12Filename, p12FilePassword, 1);
service.SendRetries = 5; //5 retries before generating notificationfailed event
service.ReconnectDelay = 5000; //5 seconds
service.Error += new NotificationService.OnError(service_Error);
service.NotificationTooLong += new NotificationService.OnNotificationTooLong(service_NotificationTooLong);
service.BadDeviceToken += new NotificationService.OnBadDeviceToken(service_BadDeviceToken);
service.NotificationFailed += new NotificationService.OnNotificationFailed(service_NotificationFailed);
service.NotificationSuccess += new NotificationService.OnNotificationSuccess(service_NotificationSuccess);
service.Connecting += new NotificationService.OnConnecting(service_Connecting);
service.Connected += new NotificationService.OnConnected(service_Connected);
service.Disconnected += new NotificationService.OnDisconnected(service_Disconnected);
//The notifications will be sent like this:
// Testing: 1...
// Testing: 2...
// Testing: 3...
// etc...
for (int i = 0; i < tokens.Count; i++)
{
//Create a new notification to send
Notification alertNotification = new Notification();
alertNotification.DeviceToken = tokens[i];
alertNotification.Payload.Alert.Body = Text;
alertNotification.Payload.Sound = "default";
alertNotification.Payload.Badge = 1;
//Queue the notification to be sent
if (service.QueueNotification(alertNotification))
Console.WriteLine("Notification Queued!");
else
Console.WriteLine("Notification Failed to be Queued!");
//Sleep in between each message
if (i < tokens.Count)
{
// Console.WriteLine("Sleeping " + sleepBetweenNotifications + " milliseconds before next Notification...");
// System.Threading.Thread.Sleep(sleepBetweenNotifications);
}
}
Console.WriteLine("Cleaning Up...");
//First, close the service.
//This ensures any queued notifications get sent befor the connections are closed
service.Close();
//Clean up
service.Dispose();
}

Resources