Validating HMAC using SecurityServiceProvider with Silex - php

I'm sending an hmac within the URL that I want to valid before allowing users access to the system. There is no user database, so its simply validating the url parameters to verity it was generated by the correct server and users can't change those parameters to access other parts of the system.
I'm wondering if anyone has done something similar with the SecurityServiceProvider. I can do this using the before middleware which is fired after routing and security firewall rules. I'd like to stop this request at the firewall though if possible.

I decided to go with silex's middleware function 'before'. I'm sure this probably isn't the best way to go about this, but my app doesn't have users or authentication so I just need to check the key is correct and return the response. If anyone has any comments on improving this I'd love to hear them.
When an external system is generating a url they must use the shared secret key defined in my config file, and accessible through $app['config']['hmac_key']. the hmac is generated on everything after the hmac in the path. so if ive got sub folders domain.com/folder/hmac/arg1/arg2/arg3. The before filter splits the route at the hmac and builds the path after that.
// Before firing the controller check the hmac matches, otherwise return 403.
$app->before(function (Request $request) use ($app) {
// 40 chars in a sha1 hmac hash
if(!preg_match('/^[0-9a-f]{40}$/i', $request->get('hmac')))
$app->abort(403, "Invalid key.");
// break the route down to the arguments used in hmac
$route = explode('hmac_', $request->get('_route'));
$route = explode('_',$route[1]);
// build the path used to generate hmac
$path = array();
foreach($route as $r)
if($v = $request->get($r))
$path[] = $v;
$path = implode('/', $path);
// Generate hmac hash from path and key
$hmac = hash_hmac("sha1", $path, $app['config']['hmac_key']);
// If the hmac's don't match return 403
if($hmac !== $request->get('hmac'))
$app->abort(403, "Invalid key.");
});

Related

How to verify the origin domain of a request in PHP (Laravel)

I am working on a Laravel app where I am building some API for other websites. But I am trying to make the implementation of my API as easy as possible. My expectation is that the user will only use this tag in the HTML head:
<script src="api.mydomain.com">
Now I have a controller on this URL that provides the source javascript with the content-type header, but before it goes there, the router will first execute my authentication middleware. Let's say it looks something like this:
public static $users = [
'client1.com',
'client2.com',
'client3.com'
];
public function handle(Request $request, Closure $next)
{
$origin = "HERE I NEED THE ORIGIN URL"; // e.g. client4.com
if ( !in_array($origin, self::$users) ) {
abort(401);
}
return $next($request);
}
As you can see from the code, I need to retrieve the $origin variable. So if a website client1.com will try to insert my javascript, it will successfully get the javascript code. If client4.com tries to access it, it will get a 401 error.
I found out methods with $_SERVER['HTTP_REFERER'] or Laravel's $request->server('HTTP_REFERER'), but this data might be spoofed, right?
In the best-case scenario, I would like to retrieve the original domain and when not available (e.g. from a private cURL request), I would like to get the IP address. And of course, I need it to be secure - clients1/2/3 paid for my API, others didn't.
How can I do it? Or is there any better method for origin authentication?
All that referer stuff can be spoofed.
Best way for paid API is to issue API calling key.
You API can display results or error depending if the client has proper API key and is Paid for.
You should also keep logs table for API calls with timestamp and clientID and IP addresses. So from time to time you can check if one of your paid client is sharing his key with others etc from call frequency and IP patterns.
Clean up this logs table from time to time to keep it small and efficient.
So I figured it out by adding headers (thanks for inspiration #jewishmoses) in the middleware handler. My Javascript is available basically to everyone, but it provides only a button, that tries to create a new element with an iframe inside (my app which also works as an API).
Let's say I have an associative array on the server, that I can dynamically fill from any database:
$clients = [
'client1' => 'paying-customer.com',
'client2' => 'also-paying-customer.com',
];
...my route for API is defined as 'api.mydomain.com/{client}' and 'api.mydomain.com/{client}/iframe' for iframed app. This handler takes care of adding headers:
public function handle(Request $request, Closure $next)
{
$client = $request->route('client',null);
$clientSet = $client !== null;
$clientAccepted = isset($clients[$client]);
if ( $clientSet and !$clientAccepted ) {
abort(401);
}
$response = $next($request);
if( $clientSet and isset($response->headers) and $response->headers instanceof ResponseHeaderBag){
$clientDomain = $clients[$client];
$response->headers->add([
'Content-Security-Policy' => "frame-ancestors https://*.$clientDomain/ https://$clientDomain/"
]);
}
return $response;
}
Now what might happen:
client1 successfully imports javascript from api.mydomain.com/client1, which will try to access api.mydomain.com/client1/iframe (also successfully)
client3 unsuccessfully tries to import javascript from api.mydomain.com/client3
client3 successfully imports javascript from api.mydomain.com/client1, which will try to access api.mydomain.com/client1/iframe (refused by headers)
Maybe there is a more elegant way to block loading the javascript, but providing my own app as API (in an iframe) is in my opinion secured enough because I can control who can use it and modern browsers will help me to stop the "thieves". This resource was most helpful to solve my problem.

php REST - prevent direct access in browser

I'm developing REST API server in PHP, which I plan to call by client application, but I want to prevent direct API accesss via browser.
E.g. say I have GET call on "HOST/api/article/id" which would return article with given id to the client application. But when I type "HOST/api/article/id" in my browser, the article shouldn't be returned - I want nothing to happen (for example just return empty page or 403).
Is this possible?
If yes, is it common practice? (I mean is it something one would normally want to do or is it obscure and/or violates HTTP/REST principles and should be avoided?)
If yes, how do I do it? (in PHP/.htaccess/etc.)
I know I could implement some kind of authorization (like API key) instead which would allow API execution only for the client applications, which I plan to do anyway.
(I'm kind of new to this so maybe my question doesn't make sense. Maybe I misunderstood something very basic about how REST/HTTP/whatever works. If so, please tell me.)
The normal approach would be:
Your client (using a public key), requests token from the server, token checks if key is valid and not blacklisted (you can expire/blacklist old keys if they are compromised)
Token is sent every time
Server only responds if there is a token
Depending what your requirements, there is a hacky way to implement this.
Have a variable, called "my_client" with value true
On each request from your application sent the variable in your headers.
Server only servers information if "my_client" variable is in the headers
The cons with this approach is, that is not really secure, because each person can see the requests they make. Therefor can notice this extra information.
Its so simple that you can write it for a minute, just as a test.
<?php
if(!$_SERVER['HTTP_MY_CLIENT']){
header("HTTP/1.1 403 FORBIDEN");
}
Extending on the concept of using a header variable, we can use it as "semi token", which will mean we will populate the value with a random value that only we can read.
So the concept is this:
Client -> Request random value
Client /sets value in each request header/
Client -> makes requests to the server.
<?php
/* A basic API token and authentication class. */
class SimpleToken
{
/* Creates a salt based on the passed key that is good for the current day */
public static function generateSalt($key)
{
return md5($key . date('Y-m-d'));
}
/* Crytographically combine the key and the salt to produce a token */
public static function generateToken($key, $content)
{
$package = $content . $key;
return crypt($package);
}
/* Generate a relatively strong SSL key */
public static function generateKey()
{
$config = array(
"digest_alg" => "sha512",
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
);
//Create a private key
$res = openssl_pkey_new($config);
//Extract the private part of the key
openssl_pkey_export($res, $private_key);
//Shorten it up for use in an API
return md5($private_key);
}
/* Verify the authenticity of the passed key/token pair */
public static function isAuthentic($key, $content, $token)
{
$package = $content . $key;
if(crypt($package, $token) == $token)
{
return true;
}
else
{
return false;
}
}
}

Laravel - How decrypt value of cookie

Hello i need to decrypt value of cookie.
My code to create and destroy:
public function setSession($id){
Cookie::queue('userId', $id, 10000);
}
public function destroySession(){
Cookie::queue(Cookie::forget('userId'));
}
But i need to get value of cookie without encrypt.
In web request context cookies are usually automatically encrypted and decrypted by the EncryptCookies middleware. So easiest option would be just to enable this middleware (and it's enabled by default in Laravel).
If you need to decrypt any value manually, the following will do the trick:
// get the encrypter service
$encrypter = app(\Illuminate\Contracts\Encryption\Encrypter::class);
// decrypt
$decryptedString = $encrypter->decrypt($encryptedString);
Check the code of the EncryptCookies middleware to learn more about what it does internally.
By default Crypt::decrypt tries to deserialize the value, and yours is not serialized and that's why you get the error. You need to pass a second argument like:
Crypt::decrypt(Cookie::get('userId'), false);

can we rely on laravel encryption for future?

We are building application where we need to store a data encrypted in database and instead of using MySql AES_ENCRYPT and AES_DECRYPT we are plaining to use laravel's inbuilt encrypt & decrypt functions.
Is it will be future proof as we don't want to loose data for future updates.
First of all, nothing is truly "future proof." In fact, we're on the verge of current encryption being rendered obsolete by quantum computing, making all current encryption methods very much not future proof.
Does Taylor have any plans of changing it in the foreseeable future? Maybe, maybe not, but the only real way of knowing is to ask him directly. He's quite active on Twitter and in other venues, so as far as business owners go, he's pretty approachable. He's also a generally nice person, so don't be afraid to ping him.
But let's take a look at the code:
public function encrypt($value, $serialize = true)
{
$iv = random_bytes(16);
// First we will encrypt the value using OpenSSL. After this is encrypted we
// will proceed to calculating a MAC for the encrypted value so that this
// value can be verified later as not having been changed by the users.
$value = \openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
// Once we get the encrypted value we'll go ahead and base64_encode the input
// vector and create the MAC for the encrypted value so we can then verify
// its authenticity. Then, we'll JSON the data into the "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (! is_string($json)) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
That's the main encrypt() function from master in the repository, and from the looks of it, it's not likely to be changed too much without completely rewriting it. And while Laravel doesn't really follow the SemVer versioning spec, it does generally follow an internally consistent versioning scheme, making the most likely times for it to change are at the whole number and first-decimal change (i.e. - 5.4 to 5.5 or 5.5 to 6.0).
However, it's worth noting that it's actually accessed via contracts and the service provider pattern (so the only time the class is actually directly referenced is in its associated ServiceProvider class). This means that you can use this one for now and if a breaking change is introduced in the future, you can copy this version into your own encryption class, replace the reference in config/app.php to Illuminate\Encryption\EncryptionServiceProvider to your new encryption service provider, and you've now preserved that method and can use it throughout your application, without making any other changes to your application.
On a bit of a side note, you can also consider writing an "encryption converter" if you find you do need to change algorithms (such as if your original algorithm is insecure) by using the old system's decrypt method to decrypt everything, then re-encrypt it all with the new system and storing it again. The application would then just use the new algorithm going forward.

Verifying JWT from Azure Active Directory

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.

Categories