I am new to the JWT tokens, and I am trying get information from a jwt token. The thing is I don't have issues when I am the one generating the token, but, for some reason, when I generate the token at JWT.io with exactly the same information, the token is different and, therefore, the validation fails. I guess the problem may come from the key I am using, as, when using a simple key like "HELLO", this disparity does not happen. This is my code:
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
use \Firebase\JWT\JWT;
use Cake\ORM\TableRegistry;
class JWTComponent extends Component
{
public function check_token($token){
$decoded = [];
$key = openssl_pkey_get_details(openssl_pkey_get_private('file://'.APP.'private.pem'))['key'];
try {
$decoded = JWT::decode($token, $key, array('HS256'));
$decoded = (array) $decoded;
} catch (Exception $e) {
$decoded = ['error' => $e->getMessage()];
}finally{
return $decoded;
}
}
public function get_token($data) {
$key = openssl_pkey_get_details(openssl_pkey_get_private('file://'.APP.'private.pem'))['key'];
return JWT::encode($data, $key);
}
}
Your intuition is good. The integrity of the token is verified by checking the signature. Tokens are signed by the party which issued them. You can use different algorithms to sign those tokens. As #jps pointed out you can have symmetric and asymmetric signing. In symmetric signing the same key is used to sign and verify the key. HS256 is a symmetric signing algorithm. You can use a certificate to do that (like in your code), but it's a bit of an overkill in my opinion. Anyway, if you want the key generated at JWT.io to be valid in your code, you will have to paste the private key in JWT.io so that it can be used for signing. Then the token should be valid in your code. That's why it worked when you used a simple string as the key.
The token that you generate in your code and in JWT.io can, in the end, look a bit differently. That is, they will both be long strings, with three parts separated by dots, but the strings does not have to be equal. This does not mean that this is a different token. The encoded JWT can differ depending on whether you used line breaks in the input, or how many spaces you used. Even though, the encoded final JWTs may look differently, these tokens have still the same value. If you decode them, you will get the same JSON, maybe slightly differently formatted.
As for the use of the symmetric algorithm, it's usually better to use asymmetric signing, so if you are able to go with that option I would definitely recommend it. Also, have a look at some libraries for PHP to issue and validate JWT, unless you write the code to learn more about JWT itself. You can find a list of libraries on JWT.io.
If you're planning to secure your APIs with JWTs, have a look at this security best practices article I wrote, to learn about the dos and don'ts of JWTs.
Related
We have an external vendor that requires us to include a bearer token in the http request header when we communicate with the API. This token shouldn't be left in the code unencrypted so where is the best place to store it? The Named Credential type doesn't seem to support storing a simple token and the Custom Setting option seems overly complicated and unnecessary. This is a single token string that will be used for every API call regardless of which user. I have searched high and low on google and haven't found an obvious solution that works.
There are some options but they're limited for your code as end user. A determined developer/sysadmin will learn the value eventually.
If you'd build a managed package you could use a protected custom setting (managed package's code could see it but not the client code, even sysadmins)
Check some of these:
https://developer.salesforce.com/page/Secure_Coding_Storing_Secrets
https://salesforce.stackexchange.com/questions/226110/what-is-the-best-way-of-storing-username-and-password-in-salesforce
https://salesforce.stackexchange.com/questions/478/using-transient-keyword-to-store-password-in-hierarchy-custom-setting
https://salesforce.stackexchange.com/questions/55008/is-encrypting-passwords-in-protected-custom-settings-a-security-requirement
You could make a custom setting with 2 text fields, 1 with encryption key and 1 with encrypted value in it. Look at Crypto class.
Blob exampleIv = Blob.valueOf('Example of IV123');
Blob key = Crypto.generateAesKey(128);
Blob data = Blob.valueOf('Data to be encrypted');
Blob encrypted = Crypto.encrypt('AES128', key, exampleIv, data);
Blob decrypted = Crypto.decrypt('AES128', key, exampleIv, encrypted);
String decryptedString = decrypted.toString();
System.assertEquals('Data to be encrypted', decryptedString);
Your initialisation vector could be org's id or something else that's easy to access and unlikely to change (I don't know if your vendor's API has test and prod endpoints but it's an added bonus that after sandbox refresh this will fail to decrypt OK until you change the custom setting... you wouldn't want to send test messages to production API), you'd generate key once & store it in setting.
is there a sample of how to implement "keep me signed in" feature in codename one? I'm guessing it involves the encrypted storage feature and maybe something else you recommend?
You can use the storage encryption if you want but generally if you just want to keep a username/password combination you can just do:
Preferences.set("username", myUserName);
Then later on in the code:
if(Preferences.get("username") != null) {
// use the username from preferences
}
I am using the xmlsec library to verify the signature of an SAML assertion. My code is almost identical to the verify4.c example provided on the xmlsec web page.
I am linking against the xmlsec-openssl lib, so using openssl as the crypto engine.
I was expecting that xmlsec would consider the signature valid only if it was signed with one of the specific certificate(s) I had loaded into the key manager.
However, the signature seems to be considered valid if signed with ANY certificate that can be verified by openssl. This means that someone could forge an SAML response just by buying a certificate from a trusted root CA and using that to sign whatever response they want.
Not only that, but the xmlsec1 command line tool provided with the library seems to do the same thing:
xmlsec1 --verify --dtd-file saml.dtd --pubkey-cert-pem my_cert.cer sample_saml_assertion.xml
...
OK
SignedInfo References (ok/all): 1/1
Manifests References (ok/all): 0/0
Actually, in an ideal world, I would be happy for any valid signing key to be used as long as I could identify the subject of the key and therefore confirm it was signed by the entity I expected. That would simplify matters when the sender of the SAML responses changed their signing key. But I have not been able to find a simple way to extract the details of the cert that was used to verify the signature.
Failing that, can I make it accept only the certificate(s) I have specified when verifying the signature?
While writing the question I realized I had not tried the --print-debug option for xmlsec1. When I tried that I found that it does indeed print the subject and issuer of the cert that was used to verify the signature.
That led me to realize that the information must be present, so it is a question of how to access it. Tracing through the code, I was able to write this little snippet which does the trick:
/* If signature is valid, then the list dsigCtx->signKey contains
the signing key, data dsigCtx->signKey->dataList contains the certificate */
xmlSecPtrListPtr keyDataList = dsigCtx->signKey->dataList;
/* Iterate through the data list to find the X509 cert */
xmlSecSize n = xmlSecPtrListGetSize(keyDataList);
xmlSecSize i;
for (i=0; i<n; i++) {
xmlSecKeyDataPtr item = xmlSecPtrListGetItem(keyDataList, i);
if (xmlSecKeyDataIsValid(item) && xmlSecKeyDataCheckId(item, xmlSecOpenSSLKeyDataX509Id)) {
/* Extract openssl cert */
X509* cert = xmlSecOpenSSLKeyDataX509GetKeyCert(item);
char cn_buff[256];
if(cert != NULL) {
/* Get the CN */
X509_NAME * subject_name = X509_get_subject_name(cert);
int nid_cn = OBJ_txt2nid("CN");
X509_NAME_get_text_by_NID(subject_name, nid_cn, cn_buff, 255);
/* Here you would compare it to the expected certificate */
fprintf(stdout, "CN=%s\n", cn_buff);
} else {
fprintf(stdout, "Failed to obtain signing key cert\n");
}
}
}
This seems a very complex way to obtain something so fundamental, so I am sure there must be an easier way.
i'm new to php and cakephp, i was following the Simple Authentication and Authorization Application tutorial from cakephp (http://book.cakephp.org/2.0/en/tutorials-and-examples/blog-auth-example/auth.html). All seem to working good.
I'm add a email confirmation to activate the account when a user subscribe. In the tutorial the password is using the blowfishpassword hasher. And i'm using it as a token in the link for the confirmation.
but i can't seem to be able to compare the link token with the password in the database...
$passwordHasher = new BlowfishPasswordHasher();
$motdepasse = $this->data['Utilisateur']['mot_passe'] = $passwordHasher->hash(
$this->data['Utilisateur']['mot_passe']
);
$link = array('controller'=>'utilisateurs','action'=>'activate',$this->Utilisateur->id
.'-'. $motdepasse);
public function activate($token) {
$token = explode('-',$token);
$user = $this->Utilisateur->find('first',array(
'conditions' => array('id' => $token[0],'Utilisateur.mot_passe' => Security::hash($token[1], 'blowfish', 'Utilisateur.mot_passe'))
));
debug($user);
debug($token[1]);
die();
}
Can you help me? thanks guys!
First of all you shouldn't send the password hash around, no matter how safe the hash possibly might be, confirmation tokens should be generated separately! Simply store it in an extra column or in a separate table.
That being said, in your activate() method you are hashing the hash again, which, in case the hash would actually be generated, would cause the comparison to fail. However the script won't genereate a hash, as you are using an invalid salt value which should result in the following warning:
Invalid salt: Utilisateur.mot_passe for blowfish Please visit http://www.php.net/crypt and read the appropriate section for building blowfish salts.
and Security::hash() will return an empty string. If you don't get such a message, then you'll need to enable the debug mode.
I'd suggest to get familiar with PHP, CakePHP, hashing and stuff first before you try to implement security related functionality!
You may want to check out https://github.com/CakeDC/users, it supports email verification and lot more out of the box.
I'm trying to authenticate against the user db of my website (CMS based) and it uses a slightly different approach at storing hashed passwords. It uses a randomly generated salt for each user. The salt is stored in the user db along with the hashed passwords. Hence, direct field-mapped authentication (as the External DB plugin does) won't work for me.
To start off, I just mirrored the DB plugin and modified the user_login() procedure to read the hashed password and the salt from the database and then hash the entered password again with the salt and match it up with the password in the database. Here's the code for my user_login() function
function user_login($username, $password) {
global $CFG;
$textlib = textlib_get_instance();
$extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->extencoding);
$extpassword = $textlib->convert(stripslashes($password), 'utf-8', $this->config->extencoding);
$authdb = $this->db_init();
// normal case: use external db for passwords
// Get user data
$sql = "SELECT
*
FROM {$this->config->table}
WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ";
$authdb->SetFetchMode(ADODB_FETCH_ASSOC);
// No DB Connection
if ( !$rs = $authdb->Execute( $sql ) ) {
$authdb->Close();
print_error('auth_dbcantconnect','auth');
return false;
}
// No records returned
if( $rs->EOF ) {
$rs->Close();
$authdb->Close();
return false;
}
// Get password
$db_password = $rs->fields['user_password'];
$salt = $rs->fields['user_salt'];
// Close DB Conn
$rs->Close();
$authdb->Close();
// Return match
return sha1( $extpassword . $salt ) == $db_password;
}
But when I try to login, username / passwords corresponding to the website (CMS) database are failing. However, the password (for the same user) that was stored in Moodle earlier on (before I tried using this custom plugin) is getting me through.
That means, either my authentication routine is failing or moodle's internal db based auth mechanism is taking precedence over it.
I've enabled ADODB debug mode - but that isn't helping either. When I enable the debug output from Server settings, the error messages are being sent prior to the page headers. Thus the login page won't display at all.
I have all other forms of authentication turned off (except for Manual which can't be turned off) and my own.
Any ideas on how to solve this issue?
Can you confirm the order that the authentication pluggins are displayed? This will determine the order in which they are used. See..
http://docs.moodle.org/en/Manage_authentication
Either way, the behaviour you're seeing suggests that your code is returning false and the fall through logic described here...
http://moodle.org/mod/forum/discuss.php?d=102070
... and here...
http://docs.moodle.org/en/Development:Authentication_plugins
... is kicking in.
Have you tried returning "true" always from your plugin to ensure that it's being called. Then, you can start returning "true" based upon other things (hard coded usernames etc). This approach will allow you to get to the point where you are either continuing to fail or seeing more targetted failures. Are you sure, for example, that it's the user_login function and not the subsequent call to update_user_record that is failing?
Finally, are you sure you're generating the salted password in the exact same way that it was created in the first place? This would be, for me, the most likely cause of the problem. Can you take control of the creation of the salted password so that you own both creation of new users and authentication of users - this would ensure that you were in sync with how the salted password and hash were generated.