Apple登录"invalid_client",使用PHP和openSSL对JWT进行签名以进行身份​​验证 [英] Apple Sign In "invalid_client", signing JWT for authentication using PHP and openSSL

查看:308
本文介绍了Apple登录"invalid_client",使用PHP和openSSL对JWT进行签名以进行身份​​验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用此

I'm trying to implement Apple sign in into an Android App using this library. The main flow is described in the documentation: the library returns an authorization code on the Android side. This authorization code has to be sent to my backend which, in turn, sends it to the Apple servers in order to get back an access token.

这里此处,为了获取访问令牌,我们需要向Apple API发送参数列表,授权代码和签名的JWT.特别是,JWT需要使用ES256算法使用.p8私钥进行签名,该私钥必须从Apple开发人员门户生成和下载. Apple文档

As described here and here, in order to obtain the access token we need to send to the Apple API a list of parameters, the authorization code and a signed JWT. In particular, JWT needs to be signed with a ES256 algorithm using a private .p8 key which has to be generated and downloaded from the Apple developer portal. Apple doc

这是我的PHP脚本:

<?php

$authorization_code = $_POST('auth_code');

$privateKey = <<<EOD
-----BEGIN PRIVATE KEY-----
my_private_key_downloaded_from_apple_developer_portal (.p8 format)
-----END PRIVATE KEY-----
EOD;

$kid = 'key_id_of_the_private_key'; //Generated in Apple developer Portal
$iss = 'team_id_of_my_developer_profile';
$client_id = 'identifier_setted_in_developer_portal'; //Generated in Apple developer Portal

$signed_jwt = $this->generateJWT($kid, $iss, $client_id, $privateKey);

$data = [
            'client_id' => $client_id,
            'client_secret' => $signed_jwt,
            'code' => $authorization_code,
            'grant_type' => 'authorization_code'
        ];
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://appleid.apple.com/auth/token');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$serverOutput = curl_exec($ch);

curl_close ($ch);

var_dump($serverOutput);

function generateJWT($kid, $iss, $sub, $key) {

    $header = [
        'alg' => 'ES256',
        'kid' => $kid
    ];
    $body = [
        'iss' => $iss,
        'iat' => time(),
        'exp' => time() + 3600,
        'aud' => 'https://appleid.apple.com',
        'sub' => $sub
    ];

    $privKey = openssl_pkey_get_private($key);
    if (!$privKey) return false;

    $payload = $this->encode(json_encode($header)).'.'.$this->encode(json_encode($body));
    $signature = '';
    $success = openssl_sign($payload, $signature, $privKey, OPENSSL_ALGO_SHA256);
    if (!$success) return false;

    return $payload.'.'.$this->encode($signature);
}

function encode($data) {
    $encoded = strtr(base64_encode($data), '+/', '-_');
    return rtrim($encoded, '=');
}

?>

问题在于Apple的回应始终是:

The problem is that the response from Apple is always:

{"error":"invalid_client"}

在此处阅读该问题似乎与openSSL有关,后者会生成一个苹果签名不正确(" OpenSSL的ES256签名结果是DER编码的ASN.1结构(其大小超过64).(不是原始R || S值)").

Reading here it seems that the problem could be related to openSSL which generates a signature which is not correct for Apple ("OpenSSL's ES256 signature result is a DER-encoded ASN.1 structure (it's size exceed 64). (not a raw R || S value)").

是否可以使用openSSL获得正确的签名?

p8格式是openssl_sign和openssl_pkey_get_private函数的正确输入吗? (我注意到,如果在 jwt.io 中使用以计算签名的jwt,则提供的.p8密钥将不起作用. )

Is the p8 format the correct input for the openssl_sign and openssl_pkey_get_private functions? (I noticed that the provided .p8 key does not work if used in jwt.io in order to compute the signed jwt.)

在openSSL文档中,我读到应该提供pem密钥,如何在.pem密钥中转换.p8?

In the openSSL documentation I read that a pem key should be provided, how can I convert a .p8 in a .pem key?

我还尝试了一些PHP库,这些库基本上使用与上述相同的步骤,例如 firebase/php-jwt lcobucci/jwt ,但Apple响应仍然是无效的客户端".

I also tried with some PHP libraries which basically use the same steps described above like firebase/php-jwt and lcobucci/jwt but the Apple response is still "invalid client".

预先感谢您的帮助,

编辑

我试图从等式中完全删除openSSL.使用从.p8生成的.pem密钥,我用jwt.io生成了一个签名的JWT.使用此签名的JWT,Apple API可以正确答复.至此,我几乎可以确定这是一个openSSL签名问题.关键问题是如何使用PHP和openSSL获得正确的ES256签名.

I tried to completely remove openSSL from the equation. Using the .pem key generated from the .p8 one I have generated a signed JWT with jwt.io. With this signed JWT the Apple API replies correctly. At this point I'm almost sure it's an openSSL signature problem. The key problem is how to obtain a proper ES256 signature using PHP and openSSL.

推荐答案

此处,问题实际上出在openSSL生成的签名中.

As indicated here, the problem is actually in the signature generated by openSSL.

使用ES256,数字签名是两个无符号整数的串联,分别表示为R和S,这是椭圆曲线(EC)算法的结果. R的长度|| S是64.

Using ES256, the digital signature is the concatenation of two unsigned integers, denoted as R and S, which are the result of the Elliptic Curve (EC) algorithm. The length of R || S is 64.

openssl_sign函数生成一个签名,该签名是DER编码的ASN.1结构(大小大于64).

The openssl_sign function generates a signature which is a DER-encoded ASN.1 structure (with size > 64).

解决方案是将DER编码的签名转换为R和S值的原始串联.在此库函数" fromDER "存在执行此类转换的操作:

The solution is to convert the DER-encoded signature into a raw concatenation of the R and S values. In this library a function "fromDER" is present which perform such a conversion:

    /**
     * @param string $der
     * @param int    $partLength
     *
     * @return string
     */
    public static function fromDER(string $der, int $partLength)
    {
        $hex = unpack('H*', $der)[1];
        if ('30' !== mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
            throw new \RuntimeException();
        }
        if ('81' === mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128
            $hex = mb_substr($hex, 6, null, '8bit');
        } else {
            $hex = mb_substr($hex, 4, null, '8bit');
        }
        if ('02' !== mb_substr($hex, 0, 2, '8bit')) { // INTEGER
            throw new \RuntimeException();
        }
        $Rl = hexdec(mb_substr($hex, 2, 2, '8bit'));
        $R = self::retrievePositiveInteger(mb_substr($hex, 4, $Rl * 2, '8bit'));
        $R = str_pad($R, $partLength, '0', STR_PAD_LEFT);
        $hex = mb_substr($hex, 4 + $Rl * 2, null, '8bit');
        if ('02' !== mb_substr($hex, 0, 2, '8bit')) { // INTEGER
            throw new \RuntimeException();
        }
        $Sl = hexdec(mb_substr($hex, 2, 2, '8bit'));
        $S = self::retrievePositiveInteger(mb_substr($hex, 4, $Sl * 2, '8bit'));
        $S = str_pad($S, $partLength, '0', STR_PAD_LEFT);
        return pack('H*', $R.$S);
    }
    /**
     * @param string $data
     *
     * @return string
     */
    private static function preparePositiveInteger(string $data)
    {
        if (mb_substr($data, 0, 2, '8bit') > '7f') {
            return '00'.$data;
        }
        while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') <= '7f') {
            $data = mb_substr($data, 2, null, '8bit');
        }
        return $data;
    }
    /**
     * @param string $data
     *
     * @return string
     */
    private static function retrievePositiveInteger(string $data)
    {
        while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') > '7f') {
            $data = mb_substr($data, 2, null, '8bit');
        }
        return $data;
    }

另一点是,应将.pem密钥提供给open_ssl_sign函数.从从Apple开发人员下载的.p8密钥开始,我已经使用openSSL创建了.pem:

Another point is that a .pem key should be provided to the open_ssl_sign function. Starting from the .p8 key downloaded from the Apple developer I have created the .pem one by using openSSL:

openssl pkcs8 -in AuthKey_KEY_ID.p8 -nocrypt -out AuthKey_KEY_ID.pem

在下面我的新 generateJWT 函数代码中,该代码使用.pem密钥和fromDER函数来转换由openSSL生成的签名:

In the following my new generateJWT function code which uses the .pem key and the fromDER function to convert the signature generated by openSSL:

    function generateJWT($kid, $iss, $sub) {
        
        $header = [
            'alg' => 'ES256',
            'kid' => $kid
        ];
        $body = [
            'iss' => $iss,
            'iat' => time(),
            'exp' => time() + 3600,
            'aud' => 'https://appleid.apple.com',
            'sub' => $sub
        ];

        $privKey = openssl_pkey_get_private(file_get_contents('AuthKey_.pem'));
        if (!$privKey){
           return false;
        }

        $payload = $this->encode(json_encode($header)).'.'.$this->encode(json_encode($body));
        
        $signature = '';
        $success = openssl_sign($payload, $signature, $privKey, OPENSSL_ALGO_SHA256);
        if (!$success) return false;

        $raw_signature = $this->fromDER($signature, 64);
        
        return $payload.'.'.$this->encode($raw_signature);
    }

希望有帮助

这篇关于Apple登录"invalid_client",使用PHP和openSSL对JWT进行签名以进行身份​​验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆