I'm trying to follow the 3rd party documentation for verifying a webhook body using the Signature header -- the 3rd party will be referred to as 3P going forward).
3P offered a sample Kotlin implementation using a java library. I am using PHP and decided to try gree/jose as my library.
As a sanity check, I've copied their sample data into my implementation, but I am still getting a false outcome.
$signature = '1IJl6VyKU4pYfqMHUd55QBNq5Etbz5a7DOCkID2Nloay76y4f02w2iMXONlyL/Bx9SkrbivOHW1l1XadkUrd5pKUK1fhpcnItukLrsK5ADQOcuEjSLBg9qJffZYooXfc7hOD/fV0sN33W2vBYJspbR3P766DwG/6IO/20f9t/DcSWa79EFZPMnsCicEArNS3iIYBtdZSX5ta5EETt7S8acHbpIlSDrTcYpo0vuz19LQ6SPQqN2LGdR+U7ZOiUQWdfMXhUgE7w94pHQzcOq1IHfw3CylUEcRR/DhrGqs4mBaagO6JpWzeqE1uTAiN579kOtSSqjblTb2AXALTQ3+TtA=='; // taken from "Signature" in headers
$payload = '{"eventId":"569886904","officeId":"132917981","eventType":"INTEGRATION_DEACTIVATED","event":{"integration":{"status":"INACTIVE","webhookId":"2bc47eed-08a0-4d18-a5c0-b7f18ab802e3","officeId":"132917981","createdDateTime":"2020-03-17T23:39:41.804Z","lastUpdatedDateTime":"2020-03-17T23:39:41.804Z"}},"createdDateTime":"2020-03-17T23:39:41.806Z"}'; // this is the body of the request sent to my application
$components = [
'kty' => 'RSA',
'e' => 'AQAB',
'n' => 'ANV-aocctqt5xDRnqomCgsO9dm4hM0Qd75TqG7z2G5Z89JQ7SRy2ok-fIJRiSU5-JfjPc3uph3gSOXyqlNoEh4YGL2R4AP7jhxy9xv0gDVtj1tExB_mmk8EUbmj8hTIrfAgEJrDeB4qMk7MkkKxhHkhLNEJEPZfgYHcHcuKjp2l_vtpiuR9Ouz0febB9K4gLozrp9KHW2K-m0z02-tSurxmmij5nnJ-CEgp0wXcCS4w4G0jve4hcLlL9FU8HKxrb0d4rMQgM3VAal6yG5pwMdtrsch7xA-occwWFC_tHgpDJGNvOJNFtuk7Cit_aom-6U6ssGF13sUtdrog2ePWjVxc=',
'kid' => '2020-03-18',
'alg' => 'RSA256',
]; // the $components array values are sourced by a separate API call to the 3P
$rsa = JOSE_JWK::decode($components); // => phpseclib\Crypt\RSA instance
$publicKey = $rsa->createKey()['publickey']; // this appears to work perfectly
$rsa->loadKey($publicKey);
var_dump($rsa->verify($payload, $rsa->sign($signature))); // bool(false)
I've been floundering with this piece of software for over 2 days now and I feel like I've tried ~100 different things (some proof). I've even tried partially abandoning the gree/jose library. Ultimately, I just need a working solution (regardless of if it is repairing this implementation or entertaining a different implementation/library).
I feel like I am probably missing a step (or two) in preparing my strings prior to calling verify(), but I am too unfamiliar with this process to identify it myself. Of course, verify() doesn't indicate if I'm getting hotter or colder.
Places I've been:
How to convert C# RSA to php?
RSA signature verification in php not working when signed from c# code
https://hotexamples.com/examples/-/-/openssl_verify/php-openssl_verify-function-examples.html
https://www.php.net/manual/en/function.openssl-verify.php
https://en.wikipedia.org/wiki/RSA_(cryptosystem)
Signing JSON objects
https://tools.ietf.org/id/draft-ietf-jose-json-web-signature-09.html
https://hotexamples.com/examples/phpseclib.crypt/RSA/verify/php-rsa-verify-method-examples.html
https://github.com/nov/jose-php
RS256 in Java means RSASSA-PKCS1-v1_5 using SHA-256
$signature = '1IJl6VyKU4pYfqMHUd55QBNq5Etbz5a7DOCkID2Nloay76y4f02w2iMXONlyL/Bx9SkrbivOHW1l1XadkUrd5pKUK1fhpcnItukLrsK5ADQOcuEjSLBg9qJffZYooXfc7hOD/fV0sN33W2vBYJspbR3P766DwG/6IO/20f9t/DcSWa79EFZPMnsCicEArNS3iIYBtdZSX5ta5EETt7S8acHbpIlSDrTcYpo0vuz19LQ6SPQqN2LGdR+U7ZOiUQWdfMXhUgE7w94pHQzcOq1IHfw3CylUEcRR/DhrGqs4mBaagO6JpWzeqE1uTAiN579kOtSSqjblTb2AXALTQ3+TtA=='; // taken from "Signature" in headers
$payload = '{"eventId":"569886904","officeId":"132917981","eventType":"INTEGRATION_DEACTIVATED","event":{"integration":{"status":"INACTIVE","webhookId":"2bc47eed-08a0-4d18-a5c0-b7f18ab802e3","officeId":"132917981","createdDateTime":"2020-03-17T23:39:41.804Z","lastUpdatedDateTime":"2020-03-17T23:39:41.804Z"}},"createdDateTime":"2020-03-17T23:39:41.806Z"}'; // this is the body of the request sent to my application
$components = [
'kty' => 'RSA',
'e' => 'AQAB',
'n' => 'ANV-aocctqt5xDRnqomCgsO9dm4hM0Qd75TqG7z2G5Z89JQ7SRy2ok-fIJRiSU5-JfjPc3uph3gSOXyqlNoEh4YGL2R4AP7jhxy9xv0gDVtj1tExB_mmk8EUbmj8hTIrfAgEJrDeB4qMk7MkkKxhHkhLNEJEPZfgYHcHcuKjp2l_vtpiuR9Ouz0febB9K4gLozrp9KHW2K-m0z02-tSurxmmij5nnJ-CEgp0wXcCS4w4G0jve4hcLlL9FU8HKxrb0d4rMQgM3VAal6yG5pwMdtrsch7xA-occwWFC_tHgpDJGNvOJNFtuk7Cit_aom-6U6ssGF13sUtdrog2ePWjVxc=',
'kid' => '2020-03-18',
'alg' => 'RSA256',
]; // the $components array values are sourced by a separate API call to the 3P
$rsa = JOSE_JWK::decode($components); // => phpseclib\Crypt\RSA instance
$rsa->setHash('sha256');
var_dump($rsa->_rsassa_pkcs1_v1_5_verify($payload, JOSE_URLSafeBase64::decode($signature)));
Related
As I understood, I have 2 ways of sending push notification to Apple APN : certificate-based, and token-based. I chose token-based
Apple guide says we need to create a token and refresh it every hour at least. So, i created a cron job that refreshes this token every hour and put it in a file on my server. Another cron job reads this token to send new pending push notification every second.
The problem comes in the refresh_token job, that I launch every hour. I use this librairy to create the JWT : https://web-token.spomky-labs.com/v/v2.x/components/signed-tokens-jws/jws-creation
Here is my code ( i just followed the guide I've just given as link) :
$algorithmManager = AlgorithmManager::create([
new ES256()
]);
// Our key.
$jwk = new JWK([
'kty' => 'EC', // *** PROBLEM HERE ***
'k' => $keyFile
]);
// The JSON Converter.
$jsonConverter = new StandardConverter();
// We instantiate our JWS Builder.
$jwsBuilder = new JWSBuilder(
$jsonConverter,
$algorithmManager
);
// The payload we want to sign. The payload MUST be a string hence we use our JSON Converter.
$payload = $jsonConverter->encode([
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'iss' => APPLE_TEAM_ID
]);
$jws = $jwsBuilder
->create()
->withPayload($payload)
->addSignature($jwk, /* with header: */['kid' => APPLE_KEY_NAME, 'alg' => 'ES256'])
->build();
This code throws an exception at ->build(); function, at the end. It says that x, y and crv parameters are not specified in the key. These parameters seem to be related to the algorithm (ES256), because when I choose the alg provided in the JWT guide, they dont ask me for these parameters.
Though, Apple didn't provide any of these informations about the key they gave me on their website. Here is their guide : https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns
The way you load the key is not correct. The key in the guide corresponds to an Octet key, not an EC one. A JWK EC key should look like the example showed in the RFC7517 section 3 (with crv, x and y parameters).
You have to convert the key file you received from Apple services into a JWK EC key.
As you already have PHP on your platform, I recommend the use of the CLI tool:
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
# Replace `/path/to/you/private/key/file.p8` with the actual path to your private key
./jose.phar key:load:key /path/to/you/private/key/file.p8
rm ./jose.phar
rm ./jose.phar.pubkey
You should get something like {"kty":"EC","crv":"P-256","d":"…","x":"…","y":"…"}.
The JWK can be loaded using the following line of code:
$jwk = JWK::createFromJson('{"kty":"EC","crv":"P-256","d":"…","x":"…","y":"…"}');
I am a completely new person to cryptography. I have tried HS256, RS256 but they got cracked. Where can I find RS512 or higher security algorithm tutorial for PHP REST?
I tried with documentation of
https://github.com/firebase/php-jwt there is documentation for RS256. but token generated using that one gets cracked here https://base64decode.org. I have tried with longer length keys as well.
The I tried with https://github.com/web-token/jwt-framework There is description for components but hardly I could understand about how to use it.
$token = array(
"iss" => $iss,
"aud" => $aud,
"iat" => $iat,
"nbf" => $nbf,
"exp" => $exp,
"data" => array(
"egfield1" => $user->egfield1,
"egfield2" => $user->egfield2,
"egfield3" => $user->egfield3,
"egfield4" => $user->egfield4
)
);
//example private
$privateKey = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn
vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9
5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB
AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz
bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J
Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1
cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5
5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck
ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe
k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb
qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k
eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm
B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM=
-----END RSA PRIVATE KEY-----
EOD;
$jwt = JWT::encode($token, $privateKey, 'RS256');
//example token RS256 generated
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJkZGQuaW4iLCJhdWQiOiJubm4uY29tIiwiaWF0IjoxNTQ4NTA0MDQzLCJuYmYiOjE1NDg1MDQwNDMsImV4cCI6MTU0ODU5MDQ0MywiZGF0YSI6eyJibGFoIGJsYWggYmxhaCI6ImJsYWggYmxhaCBibGFoIn19.Z9PdtP0ziezMxEpVBvgdsLBKndcy1fHDe5I2ypxvd2BEXSXxifLw2zJ3o3bcVZ5MogBaBbZyJOIxHA5M2XnLah90e48wVo5rXlG13edPCnPy4yt6onSfNO86Jbimr-JmQqDiN2oCeFBVCmqWu0wGxHZsyiOfp1dkBuyCJNz6mi0
issue is that it does get decoded on that site without any key
I am not able to understand am I doing wrong here or the algorithms are crackable or something. Please guide. Thank you
I think you have some concepts mixed up. JWT's, or better known as JSON Web Tokens, are built on the cryptology concept of message authentication code (MAC).
MAC gives the ability to confirm that the message came from the stated sender. It does not 'encrypt' the data thus you being able to see the direct contents of the message.
Base64 is a mutual translator for any datatype, JWT will use this encoding so you can put objects, arrays or whatever data you like inside of these tokens. Only, it ensures that no modification has been made to it.
For example, user A steals user B's JWT. He wants to access more data for more period of time so he tries to alter the exp property, he cannot resign the JWT with the MAC since he does not know the secret (in your case, the $privateKey).
When he then sends the new JWT to your server, it will throw a InvalidSignatureError which you can catch with your try catch finally blocks.
This can throw multiple errors, like expired token, so I'd suggest using catch(Exception $e) and then working with what type of error it was.
If you're looking to encrypt the contents (although it defeats the point of using JWT) you can use LibSodium, or a container I made for making it easier.
If you do not want to encrypt the $token, then do not put sensitive items inside of the token.
A QSCD can be deployed as a cloud service as long as it meets the required standards. I am not looking to, as of such, create one for legislation reasons but more of a way of my software being able to prove that the certificate come from us.
The idea is to create a PKI where the QSCD 'issues' the user the keys, which then Electronically signs a PDF file. If the public key can decode the file, it proves that it did come from that user (in this case will be a bot account that signs certificates).
I have found online solutions which entitle you to pay for such services but wanted to know how to create my own solution. I came across using openssl_* to achieve this. I took a look at a few answers to find how to create these keys.
The part I am now stuck on is how to load a PDF document and use the private key to electronically sign it and then export the new PDF file and then how to then decrypt it with the public key ensuring that it hasn't been tampered with.
My current attempt looks like this:
$config = array(
"digest_alg" => 'sha512',
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA
);
$keyPair = openssl_pkey_new($config);
$privateKey = NULL;
openssl_pkey_export($keyPair, $privateKey);
$keyDetails = openssl_pkey_get_details($keyPair);
$publicKey = $keyDetails['key'];
openssl_pkcs7_sign("toSign.pdf", "signed.pdf"); // how to sign using the private key?
penssl_public_decrypt("signed.pdf", $publicKey);
The keys I've posted here are from a container I've deleted, but were all valid keys that have been provided to me by Google.
I'm attempting to implement: https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/uploads/uploadData
The uploadData function works fine, and everything is accepted in Google Analytics. My problem lies with Google's OAuth2: https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtAuthorization
From what I've gathered, for the end-point I wish to hit, I must use an OAuth2 token, and cannot use an api key. The request doesn't accept ?key={api}, and only Bearer Authorization. Using the Service Account request, all I receive is invalid_grant. I've updated my clocks and does various miss-matches of values to no avail. Mind you, with similar keys, all other workflows work, but I don't wish to have user interaction as this is a background task.
json for key data (provided by Google):
{
"private_key_id": "825119b6ab0eabf2029a4e1cf562fa88090736a0",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANCQ+tGWdTUOL6py\nhk/KGK/ClNFQnzRrzPvOgeHCqENqeunN5LJYBlrf0OOmRzJjV67WZc3cHKu95kYr\nI+Sz0NlsmPYiwP2eMUKL5HX2JEXx/T8Bf7SWK78G7BnPKxA1fKISSftJ1IJ9neH5\nqhe4zEIB2NUcc6h3GHqBoQx4/4/dAgMBAAECgYEAsegpe2RrQEGEmVEtjpwmaK6D\nQPUTiKS36sdhdREVdMQ8anmtrg92BEhMqBNrQekJn2LU3j/22OyYo5wi9vAHohPI\nKYODw6mUemk/ULyuMGesC7nRq9sM7YnJk3KlkYrtLVR9THwAPfZ73k4UswsGFw4e\naCX6SwtNnQTHruCvCAECQQD8ZkxRf2LdP0LZYrqcB0TD2P1rYeX+IHW5sC6mdDjQ\nv6HWXjviEBfQH6kaxpUvRaSHTk1p2a5pHOjVu9DdkGXdAkEA04qc+nXH6xkBf4yE\nLODzUuAMo/QU1C+SC9AS1WbfAuRyRCkuD0SNTbK8Ec+pkqy/Q6VuvjLvvTosB9+O\nVhIyAQJASYY3RypXj2HFRHQZLiiD5JVKRUSwbdXg1WW4QS7r+gtIxpyOzyym8y61\n4SHmBW5BHlU2AdayktYkEVbz4gcVVQJBAI9JOZEwzEyDMI+btz/K0yYUmptHTgB3\nhF45/zfLKU2FPZzLo+Y1kdzKLzeFSKAQILGKUdvFFrw+tepTU88bHgECQAlp4/sy\nJ2m+zo5HsGBRP4gSxoVqiPuysT9tywJoUeo/3f+0jkDbVylTKTHpnqNk2ijFd1YS\n5ARPrKY4iXG7UoU\u003d\n-----END PRIVATE KEY-----\n",
"client_email": "42064665633-fbbnb79350js2h22e8k1s3h9t52rursu#developer.gserviceaccount.com",
"client_id": "42064665633-fbbnb79350js2h22e8k1s3h9t52rursu.apps.googleusercontent.com",
"type": "service_account"
}
The other 2 associated tags to create {Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature} are:
$header = [
'alg' => 'RS256',
'typ' => 'JWT'
];
$body = [
"iss" => "42064665633-fbbnb79350js2h22e8k1s3h9t52rursu.apps.googleusercontent.com",
"scope" => "https:\/\/www.googleapis.com\/auth\/analytics https:\/\/www.googleapis.com\/auth\/analytics.edit",
"aud" => "https:\/\/accounts.google.com\/o\/oauth2\/token",
"exp" => strtotime('1 hour'),
"iat" => strtotime('now')
];
$header = base64_encode(json_encode($header));
$body = base64_encode(json_encode($body));
The signature is defined as the private_key in the google docs link above. I've tried every excerpt of information from Java questions pertaining to this method of authentication, from escaping slashes (shown above), to omitting fields.
What have I done wrong/left out that causes {"error" : "invalid_grant"}?
I am familiar with oauth2 for objective c but here is my best shot at handling this:
invalid_grant trying to get oAuth token from google
Your internal clock may be out of sync with the google clock or your refresh token may be refreshed too much.
Make sure your client key and secret are up to date, if not revoke them and get new ones.
In PHP, make sure you are using the POST request.
Source: https://developers.google.com/analytics/devguides/reporting/core/v2/gdataAuthentication
While not a proper solution to getting JWT Service Accounts to work, I'm now storing a refresh token for the web route. Everything is working well, it's just a lot of information I need to keep in my config.yml file that I'd rather not.
https://developers.google.com/accounts/docs/OAuth2WebServer#refresh
I'm writing a library to connect to Twitter that uses Requests for PHP. I'm not sure, but I don't think that I'm building the OAuth correctly because I can't authenticate. I've double-checked it, and checked it against another existing library but still can't seem to get it right.
The code is here.
The idea is you instantiate the service, send it config parameters, and before it does the request, it generates the 'Authorization: OAuth xxx' header and adds it to said request.
$twitter = array(
'consumer_key' => '',
'consumer_secret' => '',
'access_token' => '',
'access_token_secret' => '',
'screen_name' => '_hassankhan',
'api_url' => 'https://api.twitter.com/1.1/'
);
$service = new OAuth1AService('Twitter', $twitter['api_url']);
$service->config($twitter);
$service->doGet(
'statuses/user_timeline.json',
array(
'screen_name' => '_hassankhan',
'include_entities' => 'true'
),
array(),
'raw'
);
print($service->getResult());
I would really recommend you to use one of the already made libraries like tmhOAuth which make it really easy to interact with the twitter api.
As for your problem, seems you don't sign your request correctly. At least I could not easily find out if you include all request parameters to create the signature.
The oauth_signature parameter contains a value which is generated by
running all of the other request parameters and two secret values
through a signing algorithm.
Creating the signature is described in this document: https://dev.twitter.com/docs/auth/creating-signature .