iOS7引入了新的GKLocalPlayer方法
generateIdentityVerificationSignatureWithCompletionHandler()
。
有人知道如何利用它吗? 我假设Apple服务器端会有一些公共API..
这是一个 C# WebApi 服务器端版本:
public class GameCenterController : ApiController
{
// POST api/gamecenter
public HttpResponseMessage Post(GameCenterAuth data)
{
string token;
if (ValidateSignature(data, out token))
{
return Request.CreateResponse(HttpStatusCode.OK, token);
}
return Request.CreateErrorResponse(HttpStatusCode.Forbidden, string.Empty);
}
private bool ValidateSignature(GameCenterAuth auth, out string token)
{
try
{
var cert = GetCertificate(auth.PublicKeyUrl);
if (cert.Verify())
{
var csp = cert.PublicKey.Key as RSACryptoServiceProvider;
if (csp != null)
{
var sha256 = new SHA256Managed();
var sig = ConcatSignature(auth.PlayerId, auth.BundleId, auth.Timestamp, auth.Salt);
var hash = sha256.ComputeHash(sig);
if (csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), Convert.FromBase64String(auth.Signature)))
{
// Valid user.
// Do server related user management stuff.
return true;
}
}
}
// Failure
token = null;
return false;
}
catch (Exception ex)
{
// Log the error
token = null;
return false;
}
}
private static byte[] ToBigEndian(ulong value)
{
var buffer = new byte[8];
for (int i = 0; i < 8; i++)
{
buffer[7 - i] = unchecked((byte)(value & 0xff));
value = value >> 8;
}
return buffer;
}
private X509Certificate2 GetCertificate(string url)
{
var client = new WebClient();
var rawData = client.DownloadData(url);
return new X509Certificate2(rawData);
}
private byte[] ConcatSignature(string playerId, string bundleId, ulong timestamp, string salt)
{
var data = new List<byte>();
data.AddRange(Encoding.UTF8.GetBytes(playerId));
data.AddRange(Encoding.UTF8.GetBytes(bundleId));
data.AddRange(ToBigEndian(timestamp));
data.AddRange(Convert.FromBase64String(salt));
return data.ToArray();
}
}
public class GameCenterAuth
{
public string PlayerId { get; set; }
public string BundleId { get; set; }
public string Name { get; set; }
public string PublicKeyUrl { get; set; }
public string Signature { get; set; }
public string Salt { get; set; }
public ulong Timestamp { get; set; }
}
以下是如何使用 Objective c 进行身份验证。如果您需要其他语言的版本,翻译起来应该很简单。
-(void)authenticate
{
__weak GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
if(viewController)
{
[[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController:viewController animated:YES completion:nil];
}
else if(localPlayer.isAuthenticated == YES)
{
[localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
if(error != nil)
{
return; //some sort of error, can't authenticate right now
}
[self verifyPlayer:localPlayer.playerID publicKeyUrl:publicKeyUrl signature:signature salt:salt timestamp:timestamp];
}];
}
else
{
NSLog(@"game center disabled");
}
};
}
-(void)verifyPlayer:(NSString *)playerID publicKeyUrl:(NSURL *)publicKeyUrl signature:(NSData *)signature salt:(NSData *)salt timestamp:(uint64_t)timestamp
{
//get certificate
NSData *certificateData = [NSData dataWithContentsOfURL:publicKeyUrl];
//build payload
NSMutableData *payload = [[NSMutableData alloc] init];
[payload appendData:[playerID dataUsingEncoding:NSASCIIStringEncoding]];
[payload appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSASCIIStringEncoding]];
uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
[payload appendBytes:×tampBE length:sizeof(timestampBE)];
[payload appendData:salt];
//sign
SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData); // load the certificate
SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
SecTrustRef trust;
OSStatus statusTrust = SecTrustCreateWithCertificates( certificateFromFile, secPolicy, &trust);
if(statusTrust != errSecSuccess)
{
NSLog(@"could not create trust");
return;
}
SecTrustResultType resultType;
OSStatus statusTrustEval = SecTrustEvaluate(trust, &resultType);
if(statusTrustEval != errSecSuccess)
{
NSLog(@"could not evaluate trust");
return;
}
if(resultType != kSecTrustResultProceed && resultType != kSecTrustResultRecoverableTrustFailure)
{
NSLog(@"server can not be trusted");
return;
}
SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
uint8_t sha256HashDigest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([payload bytes], (CC_LONG)[payload length], sha256HashDigest);
//check to see if its a match
OSStatus verficationResult = SecKeyRawVerify(publicKey, kSecPaddingPKCS1SHA256, sha256HashDigest, CC_SHA256_DIGEST_LENGTH, (const uint8_t *)[signature bytes], [signature length]);
CFRelease(publicKey);
CFRelease(trust);
CFRelease(secPolicy);
CFRelease(certificateFromFile);
if (verficationResult == errSecSuccess)
{
NSLog(@"Verified");
}
else
{
NSLog(@"Danger!!!");
}
}
编辑:
自2015年3月2日起,苹果现在在证书上使用SHA256而不是SHA1。 https://devforums.apple.com/thread/263789?tstart=0
我花了很多时间用 PHP 实现它。现在我想分享我的结果。
[...]
注意! 第 7 号是 PHP 中的一个陷阱,花了我几个小时。您必须仅将原始连接字符串传递给 openssl_verify() 函数。
问题中2014年7月9日的更新如何使用PHP在我的“第三方服务器”上验证GKLocalPlayer?帮助我找到了问题。
<?php
// signature, publicKeyUrl, timestamp and salt are included in the base64/json data you will receive by calling generateIdentityVerificationSignatureWithCompletionHandler.
$timestamp = $params["timestamp"]; // e.g. 1447754520194
$user_id = $params["user_id"]; // e.g. G:20010412315
$bundle_id = "com.example.test";
$public_key_url = $params["publicKeyUrl"]; // e.g. https://static.gc.apple.com/public-key/gc-prod-2.cer
$salt = base64_decode($params["salt"]); // Binary
$signature = base64_decode($params["signature"]); // Binary
// Timestamp is unsigned 64-bit integer big endian
$highMap = 0xffffffff00000000;
$lowMap = 0x00000000ffffffff;
$higher = ($timestamp & $highMap) >>32;
$lower = $timestamp & $lowMap;
$timestamp = pack('NN', $higher, $lower);
// Concatenate the string
$data = $user_id . $bundle_id . $timestamp . $salt;
// ATTENTION!!! Do not hash it! $data = hash("sha256", $packed);
// Fetch the certificate. This is dirty because it is neither cached nor verified that the url belongs to Apple.
$ssl_certificate = file_get_contents($public_key_url);
$pem = chunk_split(base64_encode($ssl_certificate), 64, "\n");
$pem = "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n";
// it is also possible to pass the $pem string directly to openssl_verify
if (($pubkey_id = openssl_pkey_get_public($pem)) === false) {
echo "invalid public key\n";
exit;
}
// Verify that the signature is correct for $data
$verify_result = openssl_verify($data, $signature, $pubkey_id, OPENSSL_ALGO_SHA256);
openssl_free_key($pubkey_id);
switch($verify_result) {
case 1:
echo "Signature is ok.\n";
break;
case 0:
echo "Signature is wrong.\n";
break;
default:
echo "An error occurred.\n";
break;
}
谢谢,@odyth。谢谢,@Lionel。 我想在这里添加Python版本(基于你的)。它有一个小缺陷 - Apple 证书未验证 - pyOpenSSL 绑定中没有这样的 API。
import urllib2
import OpenSSL
import struct
def authenticate_game_center_user(gc_public_key_url, app_bundle_id, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature):
apple_cert = urllib2.urlopen(gc_public_key_url).read()
gc_pkey_certificate = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, apple_cert)
payload = gc_player_id.encode('UTF-8') + app_bundle_id.encode('UTF-8') + struct.pack('>Q', int(gc_timestamp)) + gc_salt
try:
OpenSSL.crypto.verify(gc_pkey_certificate, gc_unverified_signature, payload, 'sha1')
print 'Signature verification is done. Success!'
except Exception as res:
print res
public_key_url = 'https://sandbox.gc.apple.com/public-key/gc-sb.cer'
player_GC_ID = 'G:1870391344'
timestamp = '1382621610281'
your_app_bundle_id = 'com.myapp.bundle_id'
with open('./salt.dat', 'rb') as f_salt:
with open('./signature.dat', 'rb') as f_sign:
authenticate_game_center_user(public_key_url, your_app_bundle_id, player_GC_ID, timestamp, f_salt.read(), f_sign.read())
添加 Python 的答案,但使用 PyCrypto 2.6(这是 Google App Engine 解决方案)。 另请注意,此处未完成下载后公共证书的验证,类似于上面使用 OpenSSL 的 python 答案。这一步真的有必要吗?如果我们检查公钥 URL 是否指向 Apple 域并且使用 ssl (https),这是否意味着它可以免受中间人攻击?
无论如何,这是代码。请注意,在连接和使用之前,二进制文本会重新转换为二进制。另外,我必须更新我的本地 python 安装以使用 PyCrypto 2.6,然后才能正常工作:
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
from base64 import b64decode
from Crypto.Util.asn1 import DerSequence
from binascii import a2b_base64
import struct
import urlparse
def authenticate_game_center_user(gc_public_key_url, app_bundle_id, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature):
apple_cert = urllib2.urlopen(gc_public_key_url).read()
#Verify the url is https and is pointing to an apple domain.
parts = urlparse.urlparse(gc_public_key_url)
domainName = ".apple.com"
domainLocation = len(parts[1]) - len(domainName)
actualLocation = parts[1].find(domainName)
if parts[0] != "https" or domainName not in parts[1] or domainLocation != actualLocation:
logging.warning("Public Key Url is invalid.")
raise Exception
cert = DerSequence()
cert.decode(apple_cert)
tbsCertificate = DerSequence()
tbsCertificate.decode(cert[0])
subjectPublicKeyInfo = tbsCertificate[6]
rsakey = RSA.importKey(subjectPublicKeyInfo)
verifier = PKCS1_v1_5.new(rsakey)
payload = gc_player_id.encode('UTF-8')
payload = payload + app_bundle_id.encode('UTF-8')
payload = payload + struct.pack('>Q', int(gc_timestamp))
payload = payload + b64decode(gc_salt)
digest = SHA.new(payload)
if verifier.verify(digest, b64decode(gc_unverified_signature)):
print "The signature is authentic."
else:
print "The signature is not authentic."
感谢代码示例,这里是 golang 解决方案:
func DownloadCert(url string) []byte {
b, err := inet.HTTPGet(url)
if err != nil {
log.Printf("http request error %s", err)
return nil
}
return b
}
func VerifySig(sSig, sGcId, sBundleId, sSalt, sTimeStamp string, cert []byte) (err error) {
sig, err := base64.StdEncoding.DecodeString(sSig)
if err != nil {
return
}
salt, err := base64.StdEncoding.DecodeString(sSalt)
if err != nil {
return
}
timeStamp, err := strconv.ParseUint(sTimeStamp, 10, 64)
if err != nil {
return
}
payload := new(bytes.Buffer)
payload.WriteString(sGcId)
payload.WriteString(sBundleId)
binary.Write(payload, binary.BigEndian, timeStamp)
payload.Write(salt)
return verifyRsa(cert, sig, payload.Bytes())
}
func verifyRsa(key, sig, content []byte) error {
cert, err := x509.ParseCertificate(key)
if err != nil {
log.Printf("parse cert error %s", err)
return err
}
pub := cert.PublicKey.(*rsa.PublicKey)
h := sha256.New()
h.Write(content)
digest := h.Sum(nil)
err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, digest, sig)
return err
}
一个小小的 http 助手
func HTTPGet(fullUrl string) (content []byte, err error) {
log.Printf("http get url %s", fullUrl)
resp, err := http.Get(fullUrl)
if err != nil {
log.Printf("url can not be reached %s,%s", fullUrl, err)
return
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("ERROR_STATUS_NOT_OK")
}
body := resp.Body
content, err = ioutil.ReadAll(body)
if err != nil {
log.Printf("url read error %s, %s", fullUrl, err)
return
}
body.Close()
return
}
测试代码
func TestVerifyFull(t *testing.T) {
cert := DownloadCert("https://sandbox.gc.apple.com/public-key/gc-sb-2.cer")
if cert == nil {
log.Printf("cert download error ")
}
sig := "sig as base64"
salt := "salt as base64"
timeStamp := "1442816155502"
gcId := "G:12345678"
bId := "com.xxxx.xxxx"
err := VerifySig(sig, gcId, bId, salt, timeStamp, cert)
log.Printf("result %v", err)
}
一个验证证书下载网址的小功能。防止从任何地方下载任何东西
func IsValidCertUrl(fullUrl string) bool {
//https://sandbox.gc.apple.com/public-key/gc-sb-2.cer
uri, err := url.Parse(fullUrl)
if err != nil {
log.Printf("not a valid url %s", fullUrl)
return false
}
if !strings.HasSuffix(uri.Host, "apple.com") {
log.Printf("not a valid host %s", fullUrl)
return false
}
if path.Ext(fullUrl) != ".cer" {
log.Printf("not a valid ext %s, %s", fullUrl, path.Ext(fullUrl))
return false
}
return true
}
require 'base64'
require 'httparty'
module GameCenter
include HTTParty
# HHTTParty settings
HTTPARTY_TIMEOUT = 10
# gc_salt and gc_unverified_signature must first be decoded with Base64.decode64(....) before this example code will work.
def authenticate_game_center_user(gc_public_key_url, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature)
# Get game center public key certificate
gc_pkey_certificate = get_gc_public_key_certificate(gc_public_key_url)
# Check public key certificate
unless public_key_certificate_is_valid?(gc_pkey_certificate) do
# Handle error
end
# Check SSL errors
unless OpenSSL.errors.empty? do
# Handle OpenSSL errors
end
# Payload building
payload = build_payload(gc_player_id, gc_timestamp, gc_salt)
# Test signature
unless signature_is_valid?(gc_pkey_certificate, gc_unverified_signature, payload) do
# Handle error
end
# Check SSL errors
unless OpenSSL.errors.empty? do
# Handle OpenSSL errors
end
# Return player ID
gc_player_id
end
def build_payload(player_id, timestamp, salt)
player_id.encode("UTF-8") + "com.myapp.bundle_id".encode("UTF-8") + [timestamp.to_i].pack("Q>") + salt
end
private
def get_gc_public_key_certificate(url)
cert = HTTParty.get(url, timeout: HTTPARTY_TIMEOUT, debug_output: Rails.env.production?)
OpenSSL::X509::Certificate.new(cert)
rescue SocketError => e
puts "Key error: " + e.inspect.to_s
end
def get_ca_certificate
OpenSSL::X509::Certificate.new(File.read('./certs/apple/verisign_class_3_code_signing_2010_ca.cer'))
end
def public_key_certificate_is_valid?(pkey_cert)
pkey_cert.verify(get_ca_certificate.public_key)
end
def signature_is_valid?(pkey_cert, signature, payload)
pkey_cert.public_key.verify(OpenSSL::Digest::SHA256.new, signature, payload)
end
end
这是我的 ruby 实现(作为模块)。感谢您的 Objective-C,这变得容易多了。
请注意,我被迫在第三方 ssl 服务网站上下载 CA 证书,因为公钥证书尚未经过 Apple 签名,并且 Apple 到目前为止还没有提供任何 CA 证书来验证沙盒游戏中心证书。
我尚未在生产中对此进行测试,但它在沙盒模式下运行良好。
感谢那些提供其他语言解决方案的人。
以下是 Scala 中解决方案的相关部分(转换为 Java 很简单):
private def verify(
signatureAlgorithm: String,
publicKey: PublicKey,
message: Array[Byte],
signature: Array[Byte]): Boolean = {
val sha1Signature = Signature.getInstance(signatureAlgorithm)
sha1Signature.initVerify(publicKey)
sha1Signature.update(message)
sha1Signature.verify(signature)
}
val x509Cert = Try(certificateFactory.generateCertificate(new ByteArrayInputStream(publicKeyBytes)).asInstanceOf[X509Certificate])
x509Cert.foreach { cert =>
signatureAlgorithm = Some(cert.getSigAlgName)
}
x509Cert.map(_.getPublicKey) match {
case Success(pk) =>
log.debug("downloaded public key successfully")
publicKey = Some(pk)
}
val buffer =
r.id.getBytes("UTF-8") ++
bundleId.getBytes("UTF-8") ++
ByteBuffer.allocate(8).putLong(r.timestamp).array() ++
Base64.decode(r.salt)
val result = verify(signatureAlgorithm.getOrElse("SHA256withRSA"), pk, buffer, Base64.decode(r.signature))
log.info("verification result {} for request {}", result, r)
其中 r 是以下对象的实例:
case class IOSIdentityVerificationRequest(
id: PlayerIdentity, // String
publicKeyURL: String,
signature: String, // base64 encoded bytes
salt: String, // base64 encoded bytes
timestamp: Long,
error: Option[String]) extends IdentityVerificationRequest
这是更新和改进的 Ruby 版本。我已经在 Apple 沙箱中对其进行了测试,但尚未在生产环境中进行测试。我还记录了在哪里获取 CA 证书,以便验证您从公钥 URL 收到的证书。
# iOS Game Center verifier for 3rd party game servers written in Ruby.
#
# *** Credits ***
# Based off of code and comments at https://stackoverflow.com/questions/17408729/how-to-authenticate-the-gklocalplayer-on-my-third-party-server
#
# *** Improvements ***
# This version uses Ruby's built in HTTP client instead of a 3rd party gem.
# It's updated to use SHA256 instead of SHA1.
# It Base64 decodes the salt and signature. If your client or server already does this then you will need to remove the calls to Base64.decode64().
# It validates that the public key URL is from apple.com.
# It has been tested with Apple's Game Center's sandbox public key URL (https://sandbox.gc.apple.com/public-key/gc-sb-2.cer) and works as of June 24th, 2015.
#
# *** Notes on public key certificate validation ***
# You will need the correct code signing CA to verify the certificate returned from the pubic key URL.
# You can download/verify the CA certificate here: https://knowledge.symantec.com/support/code-signing-support/index?page=content&actp=CROSSLINK&id=AR2170
# I have embedded the CA certificate for convenience so that you don't need to save it to your filesystem.
# When the public key URL changes in the future, you may need to update the text in the ca_certificate_text() method.
#
# *** Usage ***
# verified, reason = GameCenterVerifier.verify(...)
class GameCenterVerifier
# Verify that user provided Game Center data is valid.
# False will be returned along with a reason if any validations fail.
# Otherwise, it will return true and a nil reason if all validations pass.
def self.verify(game_center_id, public_key_url, timestamp, salt, signature, bundle_id)
salt = Base64.decode64(salt)
signature = Base64.decode64(signature)
payload = game_center_id.encode('UTF-8') + bundle_id.encode('UTF-8') + [timestamp.to_i].pack('Q>') + salt
pkey_certificate = get_public_key_certificate(public_key_url)
return false, 'Invalid public key url' unless public_key_url_is_valid?(public_key_url)
return false, 'Invalid public key certificate' unless public_key_certificate_is_valid?(pkey_certificate)
return false, 'OpenSSL errors (before signature check)' unless OpenSSL.errors.empty?
return false, 'Invalid signature' unless signature_is_valid?(pkey_certificate, signature, payload)
return false, 'OpenSSL errors (after signature check)' unless OpenSSL.errors.empty?
return true, nil
end
private
def self.get_public_key_certificate(url)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
http.use_ssl = true
http.open_timeout = 5
http.read_timeout = 5
cert = http.request(request).body
OpenSSL::X509::Certificate.new(cert)
end
def self.public_key_url_is_valid?(public_key_url)
uri = URI(public_key_url)
tokens = uri.host.split('.')
return false if uri.scheme != 'https'
return false if tokens[-1] != 'com' || tokens[-2] != 'apple'
true
end
def self.public_key_certificate_is_valid?(pkey_cert)
pkey_cert.verify(get_ca_certificate.public_key)
end
def self.signature_is_valid?(pkey_cert, signature, payload)
pkey_cert.public_key.verify(OpenSSL::Digest::SHA256.new, signature, payload)
end
def self.get_ca_certificate
OpenSSL::X509::Certificate.new(ca_certificate_text)
end
def self.ca_certificate_text
data = <<EOF
-----BEGIN CERTIFICATE-----
MIIFWTCCBEGgAwIBAgIQPXjX+XZJYLJhffTwHsqGKjANBgkqhkiG9w0BAQsFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMTMxMjEwMDAwMDAwWhcNMjMxMjA5MjM1OTU5WjB/MQsw
CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV
BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxMDAuBgNVBAMTJ1N5bWFudGVjIENs
YXNzIDMgU0hBMjU2IENvZGUgU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAJeDHgAWryyx0gjE12iTUWAecfbiR7TbWE0jYmq0v1obUfej
DRh3aLvYNqsvIVDanvPnXydOC8KXyAlwk6naXA1OpA2RoLTsFM6RclQuzqPbROlS
Gz9BPMpK5KrA6DmrU8wh0MzPf5vmwsxYaoIV7j02zxzFlwckjvF7vjEtPW7ctZlC
n0thlV8ccO4XfduL5WGJeMdoG68ReBqYrsRVR1PZszLWoQ5GQMWXkorRU6eZW4U1
V9Pqk2JhIArHMHckEU1ig7a6e2iCMe5lyt/51Y2yNdyMK29qclxghJzyDJRewFZS
AEjM0/ilfd4v1xPkOKiE1Ua4E4bCG53qWjjdm9sCAwEAAaOCAYMwggF/MC8GCCsG
AQUFBwEBBCMwITAfBggrBgEFBQcwAYYTaHR0cDovL3MyLnN5bWNiLmNvbTASBgNV
HRMBAf8ECDAGAQH/AgEAMGwGA1UdIARlMGMwYQYLYIZIAYb4RQEHFwMwUjAmBggr
BgEFBQcCARYaaHR0cDovL3d3dy5zeW1hdXRoLmNvbS9jcHMwKAYIKwYBBQUHAgIw
HBoaaHR0cDovL3d3dy5zeW1hdXRoLmNvbS9ycGEwMAYDVR0fBCkwJzAloCOgIYYf
aHR0cDovL3MxLnN5bWNiLmNvbS9wY2EzLWc1LmNybDAdBgNVHSUEFjAUBggrBgEF
BQcDAgYIKwYBBQUHAwMwDgYDVR0PAQH/BAQDAgEGMCkGA1UdEQQiMCCkHjAcMRow
GAYDVQQDExFTeW1hbnRlY1BLSS0xLTU2NzAdBgNVHQ4EFgQUljtT8Hkzl699g+8u
K8zKt4YecmYwHwYDVR0jBBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJKoZI
hvcNAQELBQADggEBABOFGh5pqTf3oL2kr34dYVP+nYxeDKZ1HngXI9397BoDVTn7
cZXHZVqnjjDSRFph23Bv2iEFwi5zuknx0ZP+XcnNXgPgiZ4/dB7X9ziLqdbPuzUv
M1ioklbRyE07guZ5hBb8KLCxR/Mdoj7uh9mmf6RWpT+thC4p3ny8qKqjPQQB6rqT
og5QIikXTIfkOhFf1qQliZsFay+0yQFMJ3sLrBkFIqBgFT/ayftNTI/7cmd3/SeU
x7o1DohJ/o39KK9KEr0Ns5cF3kQMFfo2KwPcwVAB8aERXRTl4r0nS1S+K4ReD6bD
dAUK75fDiSKxH3fzvc1D1PFMqT+1i4SvZPLQFCE=
-----END CERTIFICATE-----
EOF
end
end
这是我在 Elixir 中的实现。
def verify_login(player_id, public_key_url, timestamp, salt64, signature64, bundle_id) do
salt = Base.decode64!(salt64)
pay_load = <<player_id :: binary, bundle_id :: binary, timestamp :: big-size(64), salt :: binary>>
pkey_cert = get_public_key_certificate(public_key_url)
cert = :public_key.pkix_decode_cert(pkey_cert, :otp)
case cert do
{:OTPCertificate,
{:OTPTBSCertificate, _, _, _, _, _, _,
{:OTPSubjectPublicKeyInfo, _, key}, _, _, _}, _, _} ->
signature = Base.decode64!(signature64)
case :public_key.verify(pay_load, :sha256, signature, key) do
true ->
:ok
false ->
{:error, "apple login verify failed"}
end
end
end
def get_public_key_certificate(url) do
case HTTPoison.get(url) do
{:ok, %HTTPoison.Response{body: body}} ->
body
end
end