lcobucci / jwt

A simple library to work with JSON Web Token and JSON Web Signature
https://lcobucci-jwt.readthedocs.io/en/stable/
BSD 3-Clause "New" or "Revised" License
7.31k stars 601 forks source link

How to use with .p8 file #244

Closed mischasigtermans closed 6 years ago

mischasigtermans commented 6 years ago

Hi there,

The new MapKit JS by Apple uses JWT, however, Apple gives a .p8 certificate which only holds a private key. How can I use this to verify the signature?

Thanks!

lcobucci commented 6 years ago

As we spoke via gitter you have to convert your certificate into the keys to be used by RSA algorithm. This is achievable using openssl. Probably there's a simpler way to get to pub/private keys directly but I didn't have time to research a bit more - I just dig a bit after you called me on gitter.

1. Creating the certificate - this is not applicable to you since you already have the certificate but I'd never ask you to send it to me :) (https://stackoverflow.com/questions/8500874/how-to-generate-pkcs8-key-with-pem-encode-using-aes-128-ecb-alg-in-openssl)

Running the command bellow will create the file cert.p8 with our new PKCS8 certificate:

$ openssl genrsa | openssl pkcs8 -topk8 -v2 aes-128-ecb -out cert.p8

2. Converting it to traditional format (http://openssl.cs.utah.edu/docs/apps/pkcs8.html)

This command creates the file cert.pem

$ openssl pkcs8 -in cert.p8 -out cert.pem

3. Converting the traditional format to a RSA private key (https://stackoverflow.com/questions/2957742/how-to-convert-pkcs8-formatted-pem-private-key-to-the-traditional-format)

This command generates the file private.key to be used by the libraries.

$ openssl rsa -in cert.pem -out private.key

4. Extracting public key from private key (https://stackoverflow.com/questions/5244129/use-rsa-private-key-to-generate-public-key)

This command prints out the public key:

$ openssl rsa -in private.key -pubout

These keys are usable by any RSA algorithm available in this lib and others (https://jwt.io works fine too).

lcobucci commented 6 years ago

@Pixelstart can you please try it out and let us know if this works. If so, could you please submit a PR to extend our documentation here?

mischasigtermans commented 6 years ago

Thanks for figuring this out.

On step two I had to add -nocrypt since my .p8 file is not encrypted. On the third step however I receive the following error:

140736141493292:error:06FFF07F:digital envelope routines:CRYPTO_internal:expecting an rsa key:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.200.2/libressl-2.6/crypto/evp/p_lib.c:295:

Seems like step two is not converting it as it should.

lcobucci commented 6 years ago

I can't help you more than that since I don't have access to the .p8 file... can you generate one just for testing purposes and then revoke it?

mischasigtermans commented 6 years ago

Sure, here you go!

AuthKey_4W5TU4DR28.p8

-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgir767IOFOYHsYtNQ
wsvLeJVu3bxCLL/SURQvMZw6QumgCgYIKoZIzj0DAQehRANCAARuwGOLtHY99zLl
iyACJp6xmj6YfE8bOLxHTZGkoC/+yNgf/fBpwf5Nin2pzyM8FUOYXg1R1v2bQqJy
wHYtSkc1
-----END PRIVATE KEY-----
Spomky commented 6 years ago

This following command may help you. It requires PHP 7.1 and the OpenSSL extension.

curl -OL https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar
curl -OL https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar.pubkey

chmod +x jose.phar

./jose.phar key:convert:pkcs1 $(./jose.phar key:load:key ./AuthKey_4W5TU4DR28.p8) > key.pem

rm jose.phar*

It will convert your key in PKCS#1 PEM instead of PKSC#8. You can then use it with the library.

lcobucci commented 6 years ago

@Pixelstart I just saw that the key you gave looks like an ECDSA key instead of RSA:

$key = openssl_pkey_get_private('file://cert.p8');
var_dump(openssl_pkey_get_details($key));

/*
array(4) {
  'bits' =>
  int(256)
  'key' =>
  string(178) "-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbsBji7R2Pfcy5YsgAiaesZo+mHxP
Gzi8R02RpKAv/sjYH/3wacH+TYp9qc8jPBVDmF4NUdb9m0KicsB2LUpHNQ==
-----END PUBLIC KEY-----
"
  'ec' =>
  array(5) {
    'curve_name' =>
    string(10) "prime256v1"
    'curve_oid' =>
    string(19) "1.2.840.10045.3.1.7"
    'x' =>
    string(32) "n?c??v=?2? &???>?|O                ?GM???/??"
    'y' =>
Q???B?r?v-JG5" "???i??M?}??#<C?^
    'd' =>
    string(32) "???샅9??b?P???x?nݼB,??Q/1?:B?"
  }
  'type' =>
  int(3)
}
*/

And that's why the OpenSSL conversion to RSA doesn't work. Using what @Spomky said gives us the ECDSA private key (thanks @Spomky):

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIq++uyDhTmB7GLTUMLLy3iVbt28Qiy/0lEULzGcOkLpoAoGCCqGSM49
AwEHoUQDQgAEbsBji7R2Pfcy5YsgAiaesZo+mHxPGzi8R02RpKAv/sjYH/3wacH+
TYp9qc8jPBVDmF4NUdb9m0KicsB2LUpHNQ==
-----END EC PRIVATE KEY-----
mischasigtermans commented 6 years ago

Thanks to you both for making me understand this. So if I understand this correctly, I still have to convert the new key.pem to a .key which then has a public and private key?

mischasigtermans commented 6 years ago

So I've figured this our. Finally. I had to convert the .p8 key to a .pem key first. I've used @Spomky's tip. After this I've used @lcobucci's way of retrieving the public key from the new created .pem key.

After adding the new keys to the builder, I ran into a new error;

Mdanter\Ecc\EccFactory' not found

After searching the Issues again I figured I needed to add Ecc to 'composer.json'.

composer require mdanter/ecc:~0.3.1

Keep in mind that you need PHP 7.1 for retrieving the public key and PHP 5.6 for the added mdanter/ecc.

Thanks guys!

lcobucci commented 6 years ago

@Pixelstart cool! We're removing the dependency on that lib quite soon (for v3.3 and v4.0-alpha2) then things will be a little easier as well.

Closing this issue then 😄

Spomky commented 6 years ago

Good news.

You can still use the application to convert a key from private to public:

curl -OL https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar
curl -OL https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar.pubkey

chmod +x jose.phar

./jose.phar key:load:key ./AuthKey_4W5TU4DR28.p8 > private_key.jwk
./jose.phar key:convert:public $(cat private_key.jwk) > public_key.jwk

./jose.phar key:convert:pkcs1 $(cat private_key.jwk) > private_key.pem
./jose.phar key:convert:pkcs1 $(cat public_key.jwk) > public_key.pem

rm *.jwk
rm jose.phar*

This will create private_key.pem and public_key.pem files.

mischasigtermans commented 6 years ago

@Spomky Cool! Thanks for that additional info.

@lcobucci So will it work on PHP 7.2 then?

lcobucci commented 6 years ago

@lcobucci So will it work on PHP 7.2 then?

@Pixelstart yeap!

SirWellington commented 4 years ago

In 2020, I am seeing this error with the suggested jose script:

Not enough arguments (missing: "jwk"). 

Any ideas?

lcobucci commented 4 years ago

In 2020, I am seeing this error with the suggested jose script:

Not enough arguments (missing: "jwk"). 

Any ideas?

Perhaps opening an issue in the tool's repository might be a good start? 😁

michelvermeulen commented 3 years ago

Hey guys,

Trying this solution on my end to use an Apple generated .p8 file, but I'm stuck on this step:

./jose.phar key:convert:public private_key.jwk > public_key.jwk

In PublicKeyCommand.php line 52:

  Invalid JWK
lcobucci commented 3 years ago

@michelvermeulen you're using the tool in the wrong way, check https://github.com/lcobucci/jwt/issues/244#issuecomment-395447760 and https://github.com/lcobucci/jwt/issues/244#issuecomment-395661512

michelvermeulen commented 3 years ago

@michelvermeulen you're using the tool in the wrong way, check #244 (comment) and #244 (comment)

I actually did, sorry if that wasn't clear.

I did exactly what @Spomky suggested:

curl -OL https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar
curl -OL https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar.pubkey

chmod +x jose.phar

./jose.phar key:load:key ./AuthKey_XXXXX.p8 > private_key.jwk
./jose.phar key:convert:public private_key.jwk > public_key.jwk

And this last line doesn't work

lcobucci commented 3 years ago

@michelvermeulen you forgot to read the file to get the jwk (using cat as per the example).

Spomky commented 3 years ago

My mistake. The update line should row work fine

michelvermeulen commented 3 years ago

Thank guys! The script works this way, it didn't solve my problem with Apple Sign In but at least I can now generate a client secret and send the request.

Here's the JWT I generate to init the Oauth (don't know if this is the right place to ask, sorry):

# Your 10-character Team ID
    $iss = 'XXXXXXXXXX';

    # Your Services ID, e.g. com.aaronparecki.services
    $sub = 'com.xxxxxx.web';

    # Find the 10-char Key ID value from the portal
    $kid = 'XXXXXXXXXX';

    $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://" . app_path('private_key.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);

And when using Socialite::driver('apple')->user() on the redirect page I get a

It was not possible to parse your key, reason: error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib

karser commented 2 years ago

This worked for me. The -nocrypt option is important.

openssl pkcs8 -nocrypt -in AuthKey_XXXXXXXXXX.p8 -traditional -out AuthKey.pem
base64 -w 0 < ./AuthKey.pem

then paste its output:

$configuration = Configuration::forSymmetricSigner(
    new Sha256(),
    InMemory::base64Encoded('Paste=here=the=base64=output===')
);

Here are more details.

lcobucci commented 2 years ago

@karser this is curious... In https://github.com/lcobucci/jwt/issues/819#issuecomment-1028934299 we confirmed that PEM PKCS8 keys work out of the box with the PHP openssl extension.

The problem they had was actually related to requirements of Apple's API regarding the exp claim (must be short lived).

Is your use case different from the author of the other thread?

karser commented 2 years ago

@lcobucci Well, my case is Sign-in with Apple. I was using v3 of this library (requirement from other deps), Probably I need to try the v4.

tomastan commented 11 months ago

Hello, having shame issue, and trying to follow the instructions as shown here, but it looks @Spomky provided links are now broken (404):

https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar.pubkey

Any suggestions?

Spomky commented 11 months ago

Hi, it is still available, but not at the axact same URL https://github.com/web-token/jwt-app/releases