Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate a Paseto V2 Public Key/Token in PHP, Verify in Node.js

Preface:

What is Paseto?: https://developer.okta.com/blog/2019/10/17/a-thorough-introduction-to-paseto

  • I am using the PHP library for Paseto from here
  • I am using the Node.js Paseto lib from here

I have been able to successfully achieve the creation of a Paseto V1 token and corresponding public key using the PHP lib (with a RSA private key on the server side for keypair), and then use the public key to verify the given token on the Node.js side:

PHP Paseto Public V1:

    $privateKeyV1 = new AsymmetricSecretKey($rsaPrivate, new Version1());
    $publicKeyV1  = $privateKeyV1->getPublicKey();

    $token = (string) (new Builder())
        ->setKey($privateKeyV1)
        ->setVersion(new Version1())
        ->setPurpose(Purpose::public())
        // Set it to expire in one day
        ->setExpiration(
            (new DateTime())->add(new DateInterval('P01D'))
        )
        ->setAudience('Foo')
        ->setIssuedAt(new DateTime())
        ->setIssuer('Bar')
        ->setNotBefore()
        ->setSubject('IDP Paseto')
        ->setClaims([
            'claim' => json_decode($this->claimJSON(), true),
        ])->toString();

    return $response->withJson([
       'public_key_v1' => $publicKeyV1->raw(), 
       'token' => $token
    ]);

NodeJS Paseto Public V1:

const token    = "v1.public.sdsw5vsdf4554...............exampletoken:"; // Example paseto V1 token
const pubKey   = await createPublicKey("-----BEGIN PUBLIC KEY-----\r\npubKeyFromAbovePHP\r\n-----END PUBLIC KEY-----"); // Example public key
const response = await verify(token, pubKey);

This works great, I can verify my claim in Node.js and process what I need using the ingested data.

Now if I try the following w/ V2, calling bin2hex() on the public key to be able to store it and use it on the Node.js side, I am unable to properly verify in Node.js. I believe it has something to do with the sodium crypto binary key generation, and how the $publicKey->encode() uses Base64UrlSafe::encodeUnpadded($this->key); but am not sure.. I don't ever get the BEGIN PUBLIC KEY from the publicKey created using V2, as I believe it is just stored as binary?

PHP Paseto Public V2:

$privateKeyV2 = AsymmetricSecretKey::generate(new Version2()); 
$publicKeyV2  = $privateKeyV2->getPublicKey();

$token = (string) (new Builder())
        ->setKey($privateKeyV2)
        ->setVersion(new Version2())
        ->setPurpose(Purpose::public())
        ->setExpiration((new DateTime())->add(new DateInterval('P01D')))       
        ->setClaims([
            'claim' => json_decode($this->claimJSON(), true),
        ])->toString();

       return $response->withJson([
        'public_key_v2' => bin2hex($publicKeyV2->raw()),
        'token' => $token
       ]);

NodeJS Paseto Public V2:

const pubKey = await createPublicKey(Buffer.from('public_key_from_php_response_above', 'hex'));
const token = 'token_output_from_php_response_above';
const response = await verify(token, pubKey);
console.log(response);

I appreciate any feedback you may be able to provide. Please let me know if you have any additional questions. I tagged this with slim for the PHP framework, as I am using the PHP paseto lib within a slim project to store the public/private keys on my slim container, etc. and NodeJS within the context of a lambda.

like image 985
Woodrow Avatar asked Jan 27 '20 22:01

Woodrow


1 Answers

In case anyone still needs an answer to this - using

V2.bytesToKeyObject(publicKey) 

is what worked for me.

So instead of using the crypto library's createPublicKey method, use paseto V2's bytesToKeyObject method to generate the key to feed into V2.verify.

const { V2 } = require('paseto');

const publicKeyString = Buffer.from('Wxar8cbJRI9flcB', 'base64'); // or 'hex' if that's the encoding you used initially
const pubKey = V2.bytesToKeyObject(publicKeyString);
const payload = await V2.verify(token, publicKey);

In my case, I was using a public paseto scheme, where the signer is a Ruby on Rails app, and the verifier is a downstream node app.

In Ruby, I created the public/private key pair using mguymon's paseto gem https://github.com/mguymon/paseto.rb (v2)

like image 173
Squidonis Avatar answered Sep 19 '22 16:09

Squidonis