Verifying JWT from Azure Active Directory - php

I have followed the instructions here to obtain an access token for a web API.
https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx
I have this working but the documentation is vague when it comes to figuring out how to validate the token in PHP.
You can use the access token that is returned in the response to authenticate to a protected resources, such as a web API. Typically, the token is presented to the web API in an HTTP request using the Bearer scheme, which is described in RFC 6750. This specification explains how to use bearer tokens in HTTP requests to access protected resources.
When the web API receives and validates the token, it gives the native client application access to the web API.
How do I validate the JWT in application? I have a PHP framework which is using PHP openssl_verify() function with the token, signiture, key and algorithm but I receive error of when I use the private key from Azure with the SHA256 algorithm:
openssl_verify(): supplied key param cannot be coerced into a public key
This leads me to believe that the key I am using in PHP to validate is not correct. At the moment, I am using the private key I generated for the Active Directory Application, which happens to also be the same value I am using for the "client_secret" parameter when hitting the oauth2/token url (any other value causes no token to be generated so this is probably correct).
The key is similar to (BUT IT NOT ACTUALLY):
cLDQWERTYUI12asdqwezxctlkjpoiAn7yhjeutl8jsP=
Where I beleive openssl needs to have a certificate... if so I can't seem to find where this certificate is in the Azure portal.
What am I missing here? What is the key I should be using with openssl_verify() to verify the JWT and where do I find it in Azure?
Thanks
--
UPDATE:
I have found the public keys here: https://login.windows.net/common/discovery/keys
However I still cannot use the X5C provided to verify the signature. How do you do this in PHP?
--
UPDATE 2:
I used a converted to create a .pem file for the public key using both the 'e' and 'n' parameters. This received a public key.
Now I get OPEN SSL errors when decrypting with it:
error:0906D06C:PEM routines:PEM_read_bio:no start line

Closing this question as I have moved on from the origional issue. Updated my question with comments showing how I have progressed.
Created a new question for the new specific issue: How do I verify a JSON Web Token using a Public RSA key?
--
Just in case it helps anyone else:
For further information on a solution to obtaining a public key from Microsoft in PHP I did the following:
$string_microsoftPublicKeyURL = 'https://login.windows.net/common/discovery/keys';
$array_publicKeysWithKIDasArrayKey = loadKeysFromAzure($string_microsoftPublicKeyURL);
function loadKeysFromAzure($string_microsoftPublicKeyURL) {
$array_keys = array();
$jsonString_microsoftPublicKeys = file_get_contents($string_microsoftPublicKeyURL);
$array_microsoftPublicKeys = json_decode($jsonString_microsoftPublicKeys, true);
foreach($array_microsoftPublicKeys['keys'] as $array_publicKey) {
$string_certText = "-----BEGIN CERTIFICATE-----\r\n".chunk_split($array_publicKey['x5c'][0],64)."-----END CERTIFICATE-----\r\n";
$array_keys[$array_publicKey['kid']] = getPublicKeyFromX5C($string_certText);
}
return $array_keys;
}
function getPublicKeyFromX5C($string_certText) {
$object_cert = openssl_x509_read($string_certText);
$object_pubkey = openssl_pkey_get_public($object_cert);
$array_publicKey = openssl_pkey_get_details($object_pubkey);
return $array_publicKey['key'];
}
Its better however to cache these to disk so your not loading these them every time, but this is just a simple example of how to do this.
Then, using the array of public keys, check the JWT header for the 'kid' value to find the correct public cert to verify against and use this in parameter 3 within openssl_verify(). I used the JWT class to deal with this for me.
Using this public key array created above and the JWT class should allow you to validate microsoft JWTs.
Link to JWT class from firebase: https://github.com/firebase/php-jwt
Call JWT::Decode with param 1 of your JWT, param 2 of this public key array and param three of an array of just 'RS256'.
JWT::decode($string_JSONWebToken, $array_publicKeysWithKIDasArrayKey, array('RS256'));
This will throw an exception if the JWT is invalid or return a decrypted JWT for you to use (and check the claims).

If you want to verify the jwt then go to jwt.io
This will allow you to paste the JWT and it will then verify the header, claims, and if you add the Public key or private key (depending how the server verifies the signature) it will also verify the signature of the JWT.

Related

Verifying JWT tokens generated with PHP in Node

I'm working on implementing JWT verification in a client-side web-application that's using Webpack5. When a user is created on the backend running PHP, I create a public and private keypair for use in JWT like this and store them:
$keyPair = sodium_crypto_sign_keypair();
$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));
Then with firebase/php-jwt create the request like this:
$jwt = JWT::encode($payload, $privateKey, 'EdDSA');
However, using jose I get the following error when attempting to verify the JWT:
import * as jose from 'jose';
const { payload, protectedHeader } = await jose.jwtVerify(response.token, window.atob(response.key));
console.log(payload);
console.log(protectedHeader);
TypeError: Key must be of type CryptoKey.
I'm not understanding how it wants the key as both the base64 string and base64 presentation of the publicKey is met with the same TypeError in node. What am I doing wrong here?
Edwards-Curve Digital Signature Algorithm (EdDSA in JOSE) is not supported by WebCryptography API, as such you won't succeed with any library that uses native browser crypto. Sadly.
There's a proposal for Ed25519, Ed448, X25519, and X448 to be added in WebCrypto but it'll take a while before adoption and then implementation. When that happens jose will support this action. That being said you'll need to pass a CryptoKey. That's either imported via crypto.subtle.importKey directly, or via jose's import key functions. SubtleCrypto supports raw public key imports, jose supports PEM key imports in its wrappers only.
See Browser Support for the browser algorithm support matrix, as well as Algorithm Key Requirements for algorithm key requirements, and Type alias: KeyLike for how to obtain the right key representation.
As for the other (now deleted) suggestions, they do not take into account the algorithm you use, suggesting to use Uint8Array instead of a CryptoKey is wrong, Uint8Array use is exclusive for symmetric algorithms. Likewise, using transpiled jsonwebtoken is only remotely reliable for HMAC based algorithms.
jose only uses the runtime's native crypto layer which is your best bet at conformance and secure implementation but when running cross-runtimes you need the consider the intersection of supported algorithms on the platform.
I'm not understanding how it wants the key as both the base64 string and base64 presentation of the publicKey is met with the same TypeError in node
It's supposed to be KeyLike (interface) - in Node that's KeyObject (see crypto documentation), in Web-like environments it's CryptoKey (see WebCryptoAPI).

Firebase/JWT encryption with public Key

Hi i'm developing an architecture with an application authorization server(AS) and some client applications(CA) in PHP.
Users that are signing in a CA will login by SSO on AS and on success will be redirect to CA with some parameters stored in a JWT token.
I generate a pair of keys to implement asymmetric encryption, where private is secret and stored on the AS and public is shared around CAs.
I'm using Firebase/JWT library for PHP and i'm able to encode token generated by AS using JWT::encode($this->token, $privateKey, 'RS256') with AS private key.
At the same way the CA receives the token and decode it using
JWT::decode($jwt_token, $key, array('RS256'))
and all works fine...
But i want to introduce another secure step at the beginning.
I want generate a token in the CA(with the same library), containing referral CA infos (like app code, and other CA parameters to send to AS) before this were redirect to AS for login and i want to encrypt this token with public shared key of AS so that only AS can decrypt the token with its own private key.
In simple words Bob(CA) encrypt a message with Alice's(AS) public key so that only Alice can decrypt it with her own private key.
I'm trying to do that with same method
JWT::encode($this->token, $_ALICES_PUBLIC_KEY, 'RS256')
but it seems that the JWT::encode method only accept PRIVATE type keys and i'm getting the following error
PHP Warning: openssl_sign(): supplied key param cannot be coerced into a private key
Any help is appreciated. thanks

How to generate a well-known configuration file for open id connect?

We have an implementation of openid connect which returns an encoded id token, this works well and has been working for a while. However we are attempting to connect to it using cognito in aws and after a bit of trial and error we have found that we are missing a .well-known/openid-configuration file.
This file is meant to contain information about the calls to the openid-connect server we have including the JWK keys.
I don't understand JWK keys, this means:
1. How to generate them
2. Once generated what to do with them?
3. Does the exising code we have for the openid-connect need to change and use the JWK keys?
4. Is there any way of validating an openid-connect configuration?
I have asked something similar a while back but with no avail but revisiting.
Thanks
Kevin
What Ive already tried:
Been here: https://mkjwk.org/ and clicked on 'New Key', which returns what I assume is a web key.. however don't know what i'm meant to do with it or what the other tabs on that mean.
Expected Results:
Expecting AWS Cognito to continue and allow it to connect to our open id connect implementation. Currently just get an error with regards to a missing well known configuration file.
The openid connect server you have generates ID token that are JWS.
These tokens are signed using a private key (EC, RSA or OKP).
The well-known/openid-configuration is should contain a JSON object that indicates what algorithms are used to sign the tokens and the url to get the public keys associated to the private keys used to sign the token.
For example, the Google Account server configuration indicates the public keys can be found at https://www.googleapis.com/oauth2/v3/certs (jwks_uri parameter).
At this url, you will find a list of keys (JWKSet) formatted in JWK (JSON Web Keys). These formats (JWK and JWKSet) are the standrad representations for keys and key sets used in a JWT context (see RFC7517).
How to generate them
Normally you already have at least one private key to sign your tokens so you don’t have to generate a new one.
Once generated what to do with them?
The private key you have should be a PEM (starting with ------ PRIVATE RSA KEY ----- or similar) or DER key.
You just have to convert this key into JWK.
I created a small PHAR application (PHP) that will help to convert your keys. This app is part of the Web Token Framework but can be installed as a standalone app. Make sure you have PHP 7.1, OpenSSL and JSON extensions installed.
When the app is installed, you can execute the following command:
jose key:convert:public $(jose key:load:key PATH_TO_YOUR_KEY)
This command will convert the private key into JWK and then convert it from private to public.
The result can be shared through the jwks_uri endpoint.
Does the exising code we have for the openid-connect need to change and use the JWK keys?
No. The way you build your tokens does not means you have to change anything.
Is there any way of validating an openid-connect configuration?
As far as I know, there is no tools to validate the configuration object. You have to refer to the OpenID Connect specification.
While Florent's answer the usage of public key, I like to point you to the specification that defines the contents of JWK and specific implementation details.
OpenID Connect discovery define the discovery document. There you find jwks_uri URL as part of the metadata, which points to JSON Web Key Set
3. OpenID Provider Metadata
wks_uri
REQUIRED. URL of the OP's JSON Web Key Set [JWK] document. This
contains the signing key(s) the RP uses to validate signatures from
the OP
Now this points to RFC7517 - JSON Web Key (JWK). You can find exact details of each field of the JSON payload there. Refer its samples and appendix section to see sample contents. And your implementation must follow specification requirements defined by this.
For coding, if you are using JAVA, I suggest you to use Nimbus JOSE+JWT library. Sample can be found from this link.
For example, if you already have X509Certificate loaded, then following is the minimalistic code to generate JWK. It is embedded into a Servlet [Full source].
import Common.CertificateLoader;
import Common.Exceptions.FrameworkUncheckedException;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.JWK;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.cert.X509Certificate;
#WebServlet(urlPatterns = "/openid-configuration/jwks_uri")
public class JWKDocument extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// NOTE - LOAD YOUR CERTIFICATE HERE
final X509Certificate certificate = CertificateLoader.getCertificateLoader().getX509Certificate();
final JWK jwk;
try {
jwk = JWK.parse(certificate);
} catch (JOSEException e) {
throw new FrameworkUncheckedException("Error while loading to JWK", e);
}
resp.getOutputStream().print(jwk.toJSONString());
}
}

How to create the access token using app user id in Box-API?

I'm trying to create the access token using box app user id. I have use the following code to create the box app user
curl https://api.box.com/2.0/users \
-H "Authorization: Bearer <TOKEN>" \
-d '{"name": "Ned Stark", "is_platform_access_only": true}' \
-X POST
Then it is give the following result
{"type":"user","id":"2199107004","name":"Ned Stark","login":"AppUser_399382_9BNZHI03nJ#boxdevedition.com","created_at":"2017-08-03T00:58:04-07:00"
Is it possible to generate the access token using box app user id.?
Edited
I have generate the Public key in BOX API. Then I have file which is having Public key and Private key detail like as bellow,
{
"boxAppSettings": {
"clientID": <Client ID>,
"clientSecret": <clientsecret>,
"appAuth": {
"publicKeyID": <publickeyid>,
"privateKey": "-----BEGIN ENCRYPTED PRIVATE KEY-----\Key heresn-----END ENCRYPTED PRIVATE KEY-----\n",
"passphrase": <phrase>
}
},
"enterpriseID": <enterpriseId>
}
Then I have generate header and payload, which is as follow
$header = ["typ"=> "JWT", "alg"=>"RS256","kid"=> <public key id>];
$payload = [
"iss"=> "<client id>",
"sub"=> "<APP USER ID>",
"box_sub_type"=> "user",
"aud"=>"https://api.box.com/oauth2/token",
"jti"=>"<I don't know what is this>",
"exp"=>1428699385
];
$header = base64_encode(json_encode($header));
$payload = base64_encode(json_encode($payload));
After this I got stuck how to implement the private and public key here. Actually I'm having the JSON file which is downloaded from BOX API.
And I can't understand what is the JTI? How to add the public key and|or private key JSON file in this? How to do it?
And I have generate the private key manually as per document, as follow
openssl genrsa -aes256 -out private_key.pem 2048
Then I gave the password as "12345". And generate public key as follow,
openssl rsa -pubout -in private_key.pem -out public_key.pem
Then I added the public key in BOX-API and I made a code as follow,
$data = file_get_contents('private_key.pem');
$result = openssl_pkey_get_private($data,"12345");
print_r($result);
It gives the following result
Resource id #4
These is not looking like encrypted data. And how to implement private and public when calling box api in php.?
I won't recommend you to implement this yourself since there are already a couple of libraries implementing this protocol. However I split my answer into 2 parts the first part explains how to use an open source package to solve your problem, the second part helps you out if you want to do private keys signing.
Using a package
There are a couple of php packages that support JWT signing, at the moment of writing the one that is used most is lcobucci/jwt, but there are also other implementations found here:
https://packagist.org/search/?q=jwt
You can use composer to install it. Since version 4.0 is not documented right now I suggest you install 3.2 and have a look at the README file of that version.
You can require this in your project using: composer require lcobucci/jwt:^3.2
Your code sample suggests you need RSA256, the library has an example for that:
<?php
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Keychain; // just to make our life simpler
use Lcobucci\JWT\Signer\Rsa\Sha256; // you can use Lcobucci\JWT\Signer\Ecdsa\Sha256 if you're using ECDSA keys
$signer = new Sha256();
$keychain = new Keychain();
$token = (new Builder())
->setIssuer('http://example.com') // Configures the issuer (iss claim)
->setAudience('http://example.org') // Configures the audience (aud claim)
->setId('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item
->setIssuedAt(time()) // Configures the time that the token was issue (iat claim)
->setNotBefore(time() + 60) // Configures the time that the token can be used (nbf claim)
->setExpiration(time() + 3600) // Configures the expiration time of the token (nbf claim)
->set('uid', 1) // Configures a new claim, called "uid"
->sign($signer, $keychain->getPrivateKey('file://{path to your private key}')) // creates a signature using your private key
->getToken(); // Retrieves the generated token
Signing and verifying
When using public and private keys you always have to be sure to keep your private key safe. You can however easily publish your public key to the world without compromising security.
Signing is done using the private key, since you don't want people to be able to fake your signature, signing with the public part would make it possible for everyone to do it. This also means that the verify step always uses the public key, because everyone should be able to do it.
Doing it in PHP
The code example you provided simply loads a private key, but does not do any action with it. In order to sign you will need to use openssl_sign with your variable. Resource #xx simply means a reference to something external in php.
<?php
// Data to sign
$payload = 'TEST';
// Generate a new key, load with: openssl_pkey_get_private
$privateKey = openssl_pkey_new(array('private_key_bits' => 512)); // NOT SECURE BUT FAST
// Extract public part from private key
$details = openssl_pkey_get_details($privateKey);
// Use openssl_pkey_get_public to load from file
$publicKey = $details['key'];
// Generated by openssl_sign
$signature = null;
// Sign with private key
openssl_sign($payload, $signature, $privateKey, OPENSSL_ALGO_SHA256);
// Use base64 because the signature contains binairy data
echo 'Signed data: '.base64_encode($signature).PHP_EOL;
// Use publicKey to verify signature
$valid = openssl_verify($payload, $signature, $publicKey, OPENSSL_ALGO_SHA256);
echo 'Signature is '.($valid ? 'Valid' : 'Invalid').PHP_EOL;
What else
If you still want to implement the complete protocol I suggest you have another look at the package. And as already suggested by the comments the complete specification:
https://www.rfc-editor.org/rfc/rfc7519.txt
Last hint: JWT uses some different characters for base64 than php so be sure to handle that correctly.

php openssl: how to match the private key with the certificate

I have a self signed signature which contains the certificate itself and the private key. My purpose is to check if this private key matches with the certificate. What I do is the following:
$private = openssl_pkey_get_private("path/to/certificate");
$public = openssl_pkey_get_public("path/to/certificate");
openssl_sign("path/to/certificate", $sig, $private);
So I create the signature based on the private and the public keys from the file. So what I need to do is to compare this signature with the existing signature in the certificate. If they match, it means that the private key matches. However, I couldn't retrieve the existing signature information from the file. I was wondering if my way is a right way to do it since I have found no information on google.
thanks.
If all you want to do is check if the private key and the certificate matches, you can just call openssl_x509_check_private_key. It takes a certificate and private key as input and returns whether they both match or not. Take a look at the documentation here.
EDIT: Also, note that, the signature in the certificate is arrived using different information that composes the certificate whereas the data that you pass to the openssl_sign function is just the path to the certificate. So, even if you do end up identifying a way to extract the signature from the certificate, it still won't match the output of openssl_sign (definitely not with the $data that you are passing to openssl_sign).
I have a self signed signature which contains the certificate itself and the private key. My purpose is to check if this private key matches with the certificate. What I do is the following:
Certificates don't contain private keys. Just public keys. They're signed by a private key (which in the case of self-signed certs would be the private key corresponding to the public key contained in the cert) but they do not contain private keys.
So what I need to do is to compare this signature with the existing signature in the certificate. If they match, it means that the private key matches.
They shouldn't ever match. Check out phpseclib's X.509 parser and decode the sample cert they provide with it. There are three parts at the root level. tbsCertificate, signatureAlgorithm and signature. signature is based on tbsCertificate. So you're wanting a signature of tbsCertificate to match a signature of all three fields combined. Which is pretty much never going to happen.
As for extracting the signature itself... you can use phpseclib for that. eg.
<?php
include('File/X509.php');
$x509 = new File_X509();
$cert = $x509->loadX509('...');
echo $cert['signature']
#Karthik:
many thanks for your pointer to http://badpenguins.com/source/misc/isCertSigner.php?viewSource . It is a pity, that openssl-php library lacks the extractSignature function.
I added the code found on http://badpenguins.com/source/misc/isCertSigner.php to
my simple certificate viewer (PHP) https://github.com/Wikinaut/MySimpleCertificateViewer .

Categories