I am trying to generate a JWT for the DocuSign Embed API and I can't seem to figure out the private/public key part.
When I generate the JWT, the signature part is very short. Pasting it in jwt.io, it turns out both the header and payload are correct, but the signature part is wrong (and way too short when I compare it to what I get when I copy paste my public and rsa private keys.
Here is the relevant code:
$args = [
'envelope_id' => $_GET['envelope_id'],
'ds_return_url' => 'https://www.returnurl.be',
'starting_view' => 'envelope',
'base_path' => 'https://baseurl.be'
];
$args['integration_key'] = 'abc123';
if(isset($_GET['user_id'])) {
$args['user_id'] = $_GET['user_id'];
}
//generate jwt:
$headers = array('alg'=>'RS256','typ'=>'JWT');
$payload = array(
'iss'=>$args['integration_key'],
'sub'=>$args['user_id'],
'aud'=>'account-d.docusign.com', //prod: account.docusign.com
'iat'=>time(),
'exp'=>(time() + 6000),
'scope'=>'signature impersonation'
);
$secret = '-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAj...
-----END RSA PRIVATE KEY-----';
$jwt = generate_jwt($headers, $payload, $secret);
echo $jwt;
function generate_jwt($headers, $payload, $secret = 'secret') {
$headers_encoded = base64url_encode(json_encode($headers));
$payload_encoded = base64url_encode(json_encode($payload));
$signature = hash_hmac('SHA256', "$headers_encoded.$payload_encoded", $secret, true);
$signature_encoded = base64url_encode($signature);
$jwt = "$headers_encoded.$payload_encoded.$signature_encoded";
return $jwt;
}
function base64url_encode($str) {
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
}
Related
I am generating a RSA signature in PHP By PHPSECLIB library. According to the instructions, the signature digest must be sha256. Should the digest be set when the key is generated?
I generated Pub/Pri key by blow code: (It should be 2048 and .perm)
$new_key_pair = openssl_pkey_new(array(
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
));
openssl_pkey_export($new_key_pair, $private_key_pem);
$details = openssl_pkey_get_details($new_key_pair);
$public_key_pem = $details['key'];
file_put_contents('private.pem', $private_key_pem);
file_put_contents('public.pem', $public_key_pem);
I sign data by blow code:
$file_name = "x.json";
$sign_file = "signed/" . $file_name . ".sign";
$data = file_get_contents('files/' . $file_name);
$privatekey = file_get_contents('private.pem');
$rsa = new Crypt_RSA();
$rsa->loadKey($privatekey);
$rsa->setHash('sha256');
$signature = $rsa->sign($data);
file_put_contents($sign_file, $signature);
exit("signed.");
and verify it by
$file_name = "x.json";
$sign_file = "signed/" . $file_name . ".sign";
$publickey = file_get_contents('public.pem');
$data = file_get_contents('files/' . $file_name);
$signature = file_get_contents($sign_file);
$rsa = new Crypt_RSA();
$rsa->setHash('sha256');
$rsa->loadKey($publickey);
$verify = false;
try {
$verify = $rsa->verify($data, $signature);
} catch (Exception $ex) {
$verify = false;
}
echo "verify: " . (($verify) ? "true" : "false") . "<br>";
exit("finished.");
How to set up a digest? Is the signing process correct?
I have and example how to sign JWT key with private key and then decode it with public one on PHP
$privKey = openssl_pkey_new(array('digest_alg' => 'sha256',
'private_key_bits' => 1024,
'private_key_type' => OPENSSL_KEYTYPE_RSA));
$msg = JWT::encode('abc', $privKey, 'RS256');
$pubKey = openssl_pkey_get_details($privKey);
$pubKey = $pubKey['key'];
$decoded = JWT::decode($msg, $pubKey, array('RS256'));
$this->assertEquals($decoded, 'abc');
but here it generates and then uses generated key on the fly, what if i need to use already generated private and public key? Is it possible/enough to use file_get_contents?
Update
I found openssl_pkey_get_private and openssl_pkey_get_public i'll try to use this code with them.
Solution:
$private = openssl_pkey_get_private('file://key', 'keyphrase');
$token = \Firebase\JWT\JWT::encode('magic', $private, 'RS256');
$public = openssl_pkey_get_public('file://key.pub');
$data = \Firebase\JWT\JWT::decode($token, $public, ['RS256']);
Since I have it set to enforce signed requests, I'm running into some difficulty pulling media with a hashtag. I'm able to retrieve with other endpoints but not with the following "/tags/snowy/media/recent"
I'm not sure how or where I can get the 'min_tag_id','max_tag_id' without unsetting the "enforced signed requests" even when I do just change the "enforcing rule temporarily (which i ultimately don't want to) and put the parameters to generate the Instagram sig it still gives me the response below.
{
code: 403,
error_type: "OAuthForbiddenException",
error_message: "Invalid signed-request: Signature does not match"
}
My code to generate the signed request is below. I feel like the issue lies in the parameters that I need to set to return the correct signature.
function generate_sig($endpoint, $params, $secret) {
$sig = $endpoint;
ksort($params);
foreach ($params as $key => $val) {
$sig .= "|$key=$val";
}
return hash_hmac('sha256', $sig, $secret, false);
}
$endpoint = '/tags/snowy/media/recent';
$params = array(
'access_token' => 'TOKEN',
'count' => '10',
'min_tag_id' => '',
'max_tag_id' => ''
);
$secret = 'SECRET';
$sig = generate_sig($endpoint, $params, $secret);
$url = 'https://api.instagram.com/v1/tags/snowy/media/recent?access_token='. $access_token .'&sig='. $sig;
echo $url;
There are a few libraries for implementing JSON Web Tokens (JWT) in PHP, such as php-jwt. I am writing my own, very small and simple class but cannot figure out why my signature fails validation here even though I've tried to stick to the standard. I've been trying for hours and I'm stuck. Please help!
My code is simple
//build the headers
$headers = ['alg'=>'HS256','typ'=>'JWT'];
$headers_encoded = base64url_encode(json_encode($headers));
//build the payload
$payload = ['sub'=>'1234567890','name'=>'John Doe', 'admin'=>true];
$payload_encoded = base64url_encode(json_encode($payload));
//build the signature
$key = 'secret';
$signature = hash_hmac('SHA256',"$headers_encoded.$payload_encoded",$key);
//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature";
echo $token;
The base64url_encode function:
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
My headers and payload perfectly match the validation site's default JWT, but my signature doesn't match so my token is flagged as invalid. This standard seems really straightforward so what's wrong with my signature?
I solved it! I did not realize that the signature itself needs to be base64 encoded. In addition, I needed to set the last optional parameter of the hash_hmac function to $raw_output=true (see the docs. In short I needed to change my code from the original:
//build the signature
$key = 'secret';
$signature = hash_hmac('sha256',"$headers_encoded.$payload_encoded",$key);
//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature";
To the corrected:
//build the signature
$key = 'secret';
$signature = hash_hmac('sha256',"$headers_encoded.$payload_encoded",$key,true);
$signature_encoded = base64url_encode($signature);
//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";
echo $token;
If you want to solve it using RS256 (instead of HS256 like OP) you can use it like this:
//build the headers
$headers = ['alg'=>'RS256','typ'=>'JWT'];
$headers_encoded = base64url_encode(json_encode($headers));
//build the payload
$payload = ['sub'=>'1234567890','name'=>'John Doe', 'admin'=>true];
$payload_encoded = base64url_encode(json_encode($payload));
//build the signature
$key = "-----BEGIN PRIVATE KEY----- ....";
openssl_sign("$headers_encoded.$payload_encoded", $signature, $key, 'sha256WithRSAEncryption');
$signature_encoded = base64url_encode($signature);
//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";
echo $token;
Took me way longer than I'd like to admit
https://github.com/gradus0/appleAuth
look method $appleAuthObj->get_jwt_token()
<?php
include_once "appleAuth.class.php";
// https://developer.apple.com/account/resources/identifiers/list/serviceId -- indificator value
$clientId = ""; // com.youdomen
// your developer account id -> https://developer.apple.com/account/#/membership/
$teamId = "";
// key value show in -> https://developer.apple.com/account/resources/authkeys/list
$key = "";
// your page url where this script
$redirect_uri = ""; // example: youdomen.com/appleAuth.class.php
// path your key file, download file this -> https://developer.apple.com/account/resources/authkeys/list
$keyPath =''; // example: ./AuthKey_key.p8
try{
$appleAuthObj = new \appleAuth\sign($clientId,$teamId,$key,$redirect_uri,$keyPath);
if(isset($_REQUEST['code'])){
$jwt_token = $appleAuthObj->get_jwt_token($_REQUEST['code']);
$response = $appleAuthObj->get_response($_REQUEST['code'],$jwt_token);
$result_token = $this->read_id_token($response['read_id_token']);
var_dump($response);
var_dump($result_token);
}else{
$state = bin2hex(random_bytes(5));
echo "<a href='".$appleAuthObj->get_url($state)."'>sign</a>";
}
} catch (\Exception $e) {
echo "error: ".$e->getMessage();
}
I'm trying to use openssl_verify() to verify $payload with $publicKey.
Here's my code:
$publicKey = openssl_pkey_get_public($_POST['publicKeyURL']);
$playerID = $_POST['playerID'];
$timestamp = intval($_POST['timestamp']);
$signature = base64_decode($_POST['signature']);
$salt = base64_decode($_POST['salt']);
$payload = $playerID . $bundleID . $timestamp . $salt;
$status = openssl_verify($payload, $signature, $publicKey);
openssl_free_key($publicKey);
if ($status == 1) { /* */ }
I'm getting the following error:
openssl_verify() supplied key param cannot be coerced into a public key
The POST information is coming from an iOS app using this Game Center method.