X509Certificate2 Public Key Signature Algorithm http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 - x509certificate2

I have a certificate that has a Signature algorithm of sha256RSA but when the cert is loaded into an X509Certificate2 object the Public Key Signature algorithm is http://www.w3.org/2000/09/xmldsig#rsa-sha1 but I would expect it to be http://www.w3.org/2001/04/xmldsig-more#rsa-sha256. Below is a quick sample of how I am grabbing the cert and viewing the signature algorithm. The reason this is an issue is that Web Service Enhancements (WSE) uses this value to map to CryptoConfig to find the SignatureFormatter. Without loading the rsa-sha256 formatter we are not able to support SHA256 signatures. Moving away from WSE to something like WCF is not really an option for us at this point. I have wondered if it might have something to do with not using the Enhanced CSP but don't know how to force that either. Any help/thoughts are much appreciated.
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = String.Empty;
List<X509Certificate2> certs = LoadX509Certificates();
foreach (X509Certificate2 cert in certs)
{
textBox1.Text += cert.PublicKey.Key.SignatureAlgorithm + "\r\n";
}
}
public List<X509Certificate2> LoadX509Certificates()
{
List<X509Certificate2> certificateList = null;
// copied implementation from X509TokenProvider but don't throw on more than on certificate
X509Certificate2Collection certificateCollection = null;
X509Store x509Store = new X509Store("My",
StoreLocation.LocalMachine);
try
{
x509Store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
certificateCollection = x509Store.Certificates.Find(X509FindType.FindByThumbprint,
"93 f4 3a cd b2 6c 79 74 1c 55 4f d1 43 94 37 30 98 82 48 74", false);
if (certificateCollection.Count < 1)
{
throw new ArgumentException(
string.Format("No certificate found"));
}
foreach (X509Certificate2 cert in certificateCollection)
{
if (cert.HasPrivateKey)
{
var tempKey = cert.PrivateKey;
}
}
certificateList = certificateCollection.Cast<X509Certificate2>().ToList();
}
finally
{
if (certificateCollection != null)
{
foreach (X509Certificate2 x509Certificate2 in x509Store.Certificates)
{
if (!certificateCollection.Contains(x509Certificate2))
{
x509Certificate2.Reset();
}
}
}
x509Store.Close();
}
return (certificateList);
}
Certificate details from OpenSSL
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
2a:ac:6c:cc:22:6e:cb:97:4f:2a:4a:91:42:00:74:b4
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=\x00_\x00R\x00o\x00o\x00t\x00 \x00C\x00A\x00 \x00T\x00e\x00s\x00t\x00_\x002\x005\x006\x00.\x00e\x00t\x00.\x00l\x00o\x00c\x00a\x00l
Validity
Not Before: Feb 9 14:21:51 2015 GMT
Not After : Dec 31 23:59:59 2039 GMT
Subject: CN=\x00C\x00l\x00i\x00e\x00n\x00t\x00_\x00T\x00e\x00s\x00t\x00_\x002\x005\x006\x00.\x00e\x00t\x00.\x00l\x00o\x00c\x00a\x00l
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:af:77:1c:64:3a:ea:0b:72:df:e7:6d:c0:f6:74:
df:21:9c:e4:98:07:4c:b5:d9:7d:a3:96:88:a8:eb:
fd:bf:d6:8c:71:ac:3d:38:c2:42:b4:1d:83:18:d7:
2b:80:a2:06:3d:74:99:64:fe:a8:47:52:0e:d1:a2:
ff:8a:5d:af:a3:a9:4e:27:3e:2c:30:48:68:22:76:
ea:9a:e3:0f:d5:fa:e9:5c:35:f9:d2:dd:28:55:40:
ec:52:86:b9:c0:f9:30:c6:2d:94:0a:3b:7a:0f:00:
25:c9:eb:04:6c:85:d6:3e:6b:14:7e:a4:aa:8e:1b:
90:72:c0:76:91:f6:7b:e6:15
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:FALSE
2.5.29.1:
0g....b....O"....o.q.A0?1=0;..U...4._.R.o.o.t. .C.A. .T.e.s.t._.2.5.6...e.t...l.o.c.a.l......B.L.#......p
Signature Algorithm: sha256WithRSAEncryption
22:16:ad:44:69:27:67:93:0d:e7:43:4a:53:ee:58:ec:b1:56:
08:b2:49:fe:0d:3d:53:83:71:01:12:a7:0b:f5:d6:47:1c:5d:
f2:00:9b:61:0f:17:13:aa:24:0e:f4:db:97:85:da:47:e8:4c:
39:7a:52:ee:4b:ac:8c:f5:25:33:9f:aa:33:53:c5:8d:b3:c6:
27:e4:92:b3:b8:d2:aa:a9:b4:f0:8a:83:89:34:35:65:b2:69:
d0:4c:c1:48:f0:ea:01:a2:aa:80:d6:fb:f6:09:02:ff:00:10:
19:94:ad:20:f0:92:27:6b:6c:75:72:c4:04:a1:40:4b:16:60:
84:fe

You are confused by property name.
sha256RSA is certificate signature algorithm and is not related to embedded public key at all. SignatureAlgorithm is a part of X509Certificate2 object, not public key. What you see in the RSACryptoServiceProvider.SignatureAlgorithm property is a default signature algorithm in a particular implementation of an RSA algorithm. And as per documentation, it is always set to http://www.w3.org/2000/09/xmldsig#rsa-sha1

Related

Validate Apple StoreKit2 in-app purchase receipt jwsRepresentation in backend (node ideally, but anything works)

How can I validate an in-app purchase JWS Representation from StoreKit2 on my backend in Node?
Its easy enough to decode the payload, but I can't find public keys that Apple uses to sign these JWS/JWTs anywhere. Any other time I've worked with JWTs, you simply used the node jsonwebtoken library and passed in the signers public key or shared secret key, either configured or fetched from a JWK.
I can easily decode the JWS using node-jose j.JWS.createVerify().verify(jwsString, {allowEmbeddedKey: true}).then(r => obj = r) which gives me an object like:
{
protected: [ 'alg', 'x5c' ],
header: {
alg: 'ES256',
x5c: [
'MIIEMDueU3...',
'MII...,
'MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0...'
]
},
payload: <Buffer 7b 22 74 72 61 6e 73 61 63 74 69 6f 6e 49 64 22 3a 22 31 30 30 30 30 30 30 38 38 36 39 31 32 38 39 30 22 2c 22 6f 72 69 67 69 6e 61 6c 54 72 61 6e 73 ... 420 more bytes>,
signature: <Buffer f8 85 65 79 a1 dc 74 dd 90 80 0a a4 08 85 30 e7 22 80 4c 20 66 09 0b 84 fc f4 e5 57 53 da d5 6f 13 c6 8f 56 e8 29 67 5c 95 a6 27 33 47 1e fe e9 6e 41 ... 14 more bytes>,
key: JWKBaseKeyObject {
keystore: JWKStore {},
length: 256,
kty: 'EC',
kid: 'Prod ECC Mac App Store and iTunes Store Receipt Signing',
use: '',
alg: ''
}
}
And its easy to JSON.parse the payload and get the data I want. But, how can i verify that its authentic using the certificate chain in the x5c field
Thank you!
Finally figured this out. It turns out that we needed a "hardcoded" certificate to check against.
Apple has the certificates needed on their website. You have download the root certificate (since that's the one signing the entire chain), but you can also get the intermediate one.
Once you download one you convert it to .pem:
$ openssl x509 -inform der -in apple_root.cer -out apple_root.pem
then all you need to do is verify them against the ones in the JWS (the following is in PHP, but you should get the gist):
if (openssl_x509_verify($jws_root_cert, $downloaded_apple_root_cert) == 1){
//valid
}
Hope this helps everyone else!
It is quite challenging to piece this together from all the information, but here's how to do this in NodeJS. Note that the latest Node supports built-in crypto, which makes it much much easier. Here's my code with the necessary comments.
const jwt = require('jsonwebtoken');
const fs = require('fs');
const {X509Certificate} = require('crypto');
async function decode(signedInfo) {
// MARK: - Creating certs using Node's new build-in crypto
function generateCertificate(cert) {
// MARK: - A simple function just like the PHP's chunk_split, used in generating pem.
function chunk_split(body, chunklen, end) {
chunklen = parseInt(chunklen, 10) || 76;
end = end || '\n';
if (chunklen < 1) {return false;}
return body.match(new RegExp(".{0," + chunklen + "}", "g")).join(end);
}
return new X509Certificate(`-----BEGIN CERTIFICATE-----\n${chunk_split(cert,64,'\n')}-----END CERTIFICATE-----`);
}
// MARK: - Removing the begin/end lines and all new lines/returns from pem file for comparison
function getPemContent(path) {
return fs.readFileSync(path)
.toString()
.replace('-----BEGIN CERTIFICATE-----', '')
.replace('-----END CERTIFICATE-----', '')
.replace(/[\n\r]+/g, '');
}
// MARK: - The signed info are in three parts as specified by Apple
const parts = signedInfo.split('.');
if (parts.length !== 3) {
console.log('The data structure is wrong! Check it! ');
return null;
}
// MARK: - All the information needed for verification is in the header
const header = JSON.parse(Buffer.from(parts[0], "base64").toString());
// MARK: - The chained certificates
const certificates = header.x5c.map(cert => generateCertificate(cert));
const chainLength = certificates.length;
// MARK: - Leaf certificate is the last one
const leafCert = header.x5c[chainLength-1];
// MARK: - Download .cer file at https://www.apple.com/certificateauthority/. Convert to pem file with this command line: openssl x509 -inform der -in AppleRootCA-G3.cer -out AppleRootCA-G3.pem
const AppleRootCA = getPemContent('AppleRootCA-G3.pem');
// MARK: - The leaf cert should be the same as the Apple root cert
const isLeafCertValid = AppleRootCA === leafCert;
if (!isLeafCertValid) {
console.log('Leaf cert not valid! ');
return null;
}
// MARK: If there are more than one certificates in the chain, we need to verify them one by one
if (chainLength > 1) {
for (var i=0; i < chainLength - 1; i++) {
const isCertValid = certificates[i].verify(certificates[i+1].publicKey);
if (!isCertValid) {
console.log(`Cert ${i} not valid! `);
return null;
}
}
}
return jwt.decode(signedInfo);
}
Good luck!
You need to validate the header and the payload with the sign like says in the WWDC videos:
https://developer.apple.com/videos/play/wwdc2022/10040/
https://developer.apple.com/videos/play/wwdc2021/10174/
But is more complicate than you think to do this if you don't have the knownledge about JWT because there is no documentation from Apple to do this, they only say to you "use your favorite cryptographic library to verify the data".
So after doing a lot of research, finally I found a solution using PHP 8.1 with Laravel.
First you need to install this library https://github.com/firebase/php-jwt:
composer require firebase/php-jwt
Then you need to implement the following method in order to validate the JWT from the transaction:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
...
public function validateJwt($jwt)
{
$components = explode('.', $jwt);
if (count($components) !== 3) {
throw new \Exception('JWS string must contain 3 dot separated component.');
}
$header = base64_decode($components[0]);
$headerJson = json_decode($header,true);
$this->validateAppleRootCA($headerJson);
$jwsParsed = (array) $this->decodeCertificate($jwt, $headerJson, 0);
for ($i = 1; $i < count($headerJson) - 1; $i++) {
$this->decodeCertificate($jwt, $headerJson, $i);
}
// If the signature and the jws is invalid, it will thrown an exception
// If the signature and the jws is valid, it will create the $decoded object
// You can use the $decoded object as an array if you need:
$transactionId = $jwsParsed['transactionId'];
}
private function validateAppleRootCA($headerJson)
{
$lastIndex = array_key_last($headerJson['x5c']);
$certificate = $this->getCertificate($headerJson, $lastIndex);
// As Oliver Zhang says in their NodeJS example, download the .cer file at https://www.apple.com/certificateauthority/. Convert to pem file with this command line: openssl x509 -inform der -in AppleRootCA-G3.cer -out AppleRootCA-G3.pem
// In Laravel, this location is at storage/keys/AppleRootCA-G3.pem
$appleRootCA = file_get_contents(storage_path('keys/AppleRootCA-G3.pem'));
if ($certificate != $appleRootCA) {
throw new \Exception('jws invalid');
}
}
private function getCertificate($headerJson, $certificateIndex)
{
$certificate = '-----BEGIN CERTIFICATE-----'.PHP_EOL;
$certificate .= chunk_split($headerJson['x5c'][$certificateIndex],64,PHP_EOL);
$certificate .= '-----END CERTIFICATE-----'.PHP_EOL;
return $certificate;
}
private function decodeCertificate($jwt, $headerJson, $certificateIndex)
{
$certificate = $this->getCertificate($headerJson, 0);
$cert_object = openssl_x509_read($certificate);
$pkey_object = openssl_pkey_get_public($cert_object);
$pkey_array = openssl_pkey_get_details($pkey_object);
$publicKey = $pkey_array['key'];
$jwsParsed = null;
try {
$jwsDecoded = JWT::decode($jwt, new Key($publicKey, 'ES256'));
$jwsParsed = (array) $jwsDecoded;
} catch (SignatureInvalidException $e) {
throw new \Exception('signature invalid');
}
return $jwsParsed;
}
To call the function, you need to pass the jwt from the transaction:
$jwt = 'eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUl...';
validateJwt($jwt);
The JWS x5c header parameter contains the entire certificate chain used to sign and validate the JWS. There is no need to fetch any other certificates or keys.
The RFC specifies that the certificate corresponding to the public key that was used to sign the JWS must be the first certificate.
You can extract the public key from this certificate and use it to verify the JWS signature. There is some guidance on this in this answer
One of the great improvements in StoreKit2 is that you are no longer required to use a server to validate in app purchase transactions securely.
Apple's WWDC 2021 session on StoreKit2 describes the content of the JWS and also shows how to validate on device that the JWS was actually generated for that device.
But, what if you do want to validate the transaction on a server? Since the x5c claim contains the certificate chain, an attacker could sign a forged JWS with their own certificate and include that certificate in the x5c claim.
The answer is that you have your app send the original transaction id to your server along with any other information you need, such as the user's account identifier. Your server can then request the corresponding JWS from Apple and validate the signature of the returned JWS.
As the JWS was fetched from Apple by your server code it can be sure that it is not a spoofed JWS.
If possible, include an appAccountToken in your purchase request and either determine the expected token value based on the user's authentication to your server or (less effective) have your app supply the token when it supplies the original transaction id. You can then verify the token value in the JWS matches the expected value. This makes it harder for an attacker to replay some other purchase event.

Connection to AWS IoT with M2MQTT from .net core

I managed to manually create the AWS IoT config, downloaded the certs and create a console app that could subscribe to a topic. Im now trying to automate the thing creation, which results in the certificate keys being provided by AWS as strings. Im not sure how to use these. I have the root ca downloaded already, which I assume I use for all things.
My file based cert subscriber looks like this:
Console.WriteLine("AWS IOT Dotnet core message listener starting");
string iotendpoint = "blahblah-ats.iot.ap-southeast-2.amazonaws.com";
int BrokerPort = 8883;
string Topic = "topic_1/";
var CaCert = X509Certificate.CreateFromCertFile(#"root-CA.crt");
var ClientCert = new X509Certificate2(#"device.pfx", "password");
var IotClient = new MqttClient(iotendpoint, BrokerPort, true, CaCert, ClientCert, MqttSslProtocols.TLSv1_2);
try
{
IotClient.Connect(Guid.NewGuid().ToString());
Console.WriteLine("Connected to AWS IOT");
IotClient.MqttMsgPublishReceived += Client_MqttMsgPublishReceived;
IotClient.MqttMsgSubscribed += Client_MqttMsgSubscribed;
IotClient.Subscribe(new string[] { Topic }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE });
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
To load the cert from file, I tried this:
var keyText = File.ReadAllText("keys.json");
var keys = JsonConvert.DeserializeObject<Keys>(keyText);
var bytes = Encoding.ASCII.GetBytes(keys.PrivateKey.ToCharArray());
var ClientCert = new X509Certificate2(bytes);
with:
class Keys {
public string PublicKey {get;set;}
public string PrivateKey {get;set;}
}
and the keys from AWS in a json file :
{
"PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA4mh2PQ581XN9BmoCvDjlaktm/6gQgqGBItZThcQVMTjveU8H\npjOU2E/9lq7vmdO+96NuuMr9MKtFD+ZWtVExLjMq9hH0MvIvosVt9+6Ggcwz7Kdr\nigprfBMVORV0rgcK+nsd2DmBNrs339fqbTn5UAIFFBpqkNReW7LMl9h6g8hu4aYQ\nJTohDwSmgmNJKlzMJGtVfPggqt+bBi3lUf9NEOEz...
-----END RSA PRIVATE KEY-----\n",
"PublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4mh2PQ581XN9BmoCvDjl\naktm/6gQgqGBItZThcQVMTjveU8HpjOU2E/9lq7vmdO+96NuuMr9MKtFD+ZWtVEx\nLjMq9hH0MvIvosVt9+6Ggcwz7K...
-----END PUBLIC KEY-----\n"
}
Im getting an error loading the cert:
An unhandled exception of type 'Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException' occurred in System.Security.Cryptography.X509Certificates.dll: 'Cannot find the requested object.'
Can anyone see anything obviously wrong here? I dont understand certificates...
Update:
Using the PEM text produced by the AWS SDK is more correct, but I still get an error connecting - M2MQTT says there is a cert problem, it has no private key. Does it need it?
var pemText = File.ReadAllText("thing.crt");
var bytes = Encoding.ASCII.GetBytes(pemText);
var ClientCert = new X509Certificate2(bytes);
Final hacked together solution looks like this:
var keyText = File.ReadAllText("keys.json"); // saved from AWS SDK when creating IoT Cert.
var keys = JsonConvert.DeserializeObject<Keys>(keyText);
var rsa = RsaHelper.PrivateKeyFromPem(keys.PrivateKey);
var pemText = File.ReadAllText("thing.crt");
var bytes = Encoding.ASCII.GetBytes(pemText);
var ClientCert = new X509Certificate2(bytes);
ClientCert = ClientCert.CopyWithPrivateKey(rsa);
ClientCert = new X509Certificate2(ClientCert.Export(X509ContentType.Pfx,"12345678"), "12345678");
RSAHelper from https://github.com/dejanstojanovic/dotnetcore-token-authentication/blob/asymmetric_rsahelper/Sample.Core.Common/Helpers/RsaHelper.cs
Last trick to Export and Import the PFX from https://github.com/aspnet/KestrelHttpServer/issues/2960 to solve error: "No credentials are available in the security package"
Sidebar - why do we (as an industry) always take something conceptually simple and make it so fricken complicated? :)

"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 to use setCACert() of WiFiClientSecure with HTTPClient library?

I'd like to set my CA root cert (currently available via WiFiClientSecure library) and use convenient HTTPClient library for making request.
How to do that? In the example there is only showed how to use WiFiClientSecure with manually written request.
It's way to late but this post is one one of the ones that gets returned by google so it's a good way to help others like me who are just starting out with this.
It turns out that all the posts about WifiClientSecure are obsolete (or simply unnecessary).
Here's a working example using the default HTTPClient. (not HttpClient)
It does require setting the certificate's sha1 fingerprint in the begin statement.
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
HTTPClient http;
Serial.print("[HTTPS] begin...\n");
http.begin("https://some.secure_server.com/auth/authorise", "2F 2A BB 23 6B 03 89 76 E6 4C B8 36 E4 A6 BF 84 3D DA D3 9F");
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
int httpCode = http.POST("user_id=mylogin&user_password=this%20is%20my%20%24ecret%20pa%24%24word");
if (httpCode > 0) {
http.writeToStream(&Serial);
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] ... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTP] ... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
delay(10000);
}

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

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

Resources