如何在我的“第三方服务器”上验证 GKLocalPlayer?

问题描述 投票:0回答:10

iOS7引入了新的GKLocalPlayer方法

generateIdentityVerificationSignatureWithCompletionHandler()

有人知道如何利用它吗? 我假设Apple服务器端会有一些公共API..

ios authentication ios7 game-center
10个回答
13
投票

这是一个 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; }
}

9
投票

以下是如何使用 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:&timestampBE 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


5
投票

我花了很多时间用 PHP 实现它。现在我想分享我的结果。

文档

您可以在Apple找到一个非常简单的文档:https://developer.apple.com/library/ios/documentation/GameKit/Reference/GKLocalPlayer_Ref/index.html#//apple_ref/occ/instm/GKLocalPlayer/generateIdentityVerificationSignatureWithCompletionHandler

[...]

  1. 使用第三方服务器上的publicKeyURL下载公钥。
  2. 向适当的签名机构验证公钥是否由 Apple 签名。
  3. 检索玩家的playerID和bundleID。
  4. 按照列出的顺序将以下信息连接到数据缓冲区中:
    • UTF-8格式的playerID参数
    • UTF-8格式的bundleID参数
    • Big-Endian UInt-64 格式的时间戳参数
    • 盐参数
  5. 为缓冲区生成 SHA-256 哈希值。
  6. 使用步骤3中下载的公钥,验证步骤7中生成的哈希值是否与API提供的签名参数匹配。

注意! 第 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;
}

3
投票

谢谢,@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())

3
投票

添加 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."

2
投票

感谢代码示例,这里是 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
}

2
投票
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 证书来验证沙盒游戏中心证书。

我尚未在生产中对此进行测试,但它在沙盒模式下运行良好。


0
投票

感谢那些提供其他语言解决方案的人。

以下是 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

0
投票

这是更新和改进的 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

0
投票

这是我在 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
© www.soinside.com 2019 - 2024. All rights reserved.