In implementing an oauth2 stack utilizing passport and oauth2orize, in this case the issue is specifically in utilizing the oauth2orize jwt bearer. The oauth2orize jwt bearer is great in getting everything going, however it has the RSA SHA pieces marked as to do.
In attempting to put in the pieces for the RSA SHA encryption handling, I cannot get the signature to verify as verifier.verify always seems to return false. If anyone has cleared this hurdle, a little help would be super.
What I've done:
Created the private / public keys:
openssl genrsa -out private.pem 1024
//extract public key
openssl rsa -in private.pem -out public.pem -outform PEM -pubout
now the data to sign:
{"alg":"RS256","typ":"JWT"}{"iss": "myclient"}
I've tried multiple ways as to how to sign this, too many to list here, but my understanding of the correct signature is to sign the bas64 encoding of these items, so i ran base64 on {"alg":"RS256","typ":"JWT"} and base64 on {"iss": "myclient"} then ran base64 on those encodings. So the result is:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
eyJpc3MiOiAibXljbGllbnQifQ
then encode:
{eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9}.{eyJpc3MiOiAibXljbGllbnQifQ}
which gives me:
e2V5SmhiR2NpT2lKU1V6STFOaUlzSW5SNWNDSTZJa3BYVkNKOX0ue2V5SnBjM01pT2lBaWJYbGpiR2xsYm5RaWZRfQ
At this point I sign the above base64 by doing:
openssl sha -sha256 -sign priv.pem < signThis > signedData
Then I run base64 on that to get the data to pass into the signature part of the assertion.
I then pass in the object:
{
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiAibXljbGllbnQifQ.signedData"
}
now in the code base I have:
var crypto = require('crypto')
, fs = require('fs')
, pub = fs.readFileSync('/path/to/pub.pem')
, verifier = crypto.createVerify("RSA-SHA256");
verifier.update(JSON.stringify(data));
var result = verifier.verify(pub, signature, 'base64');
console.log('vf: ', result);
however, result is always false.
I do properly receive the data, the signature variable in the code is a match for what I'm passing in, I just always receive false and have exhausted all options I can think of on how to tweak this to get verifier.verify to return true. Thank you for the time and help!
I am not sure if this is exactly what you were looking for, but this will successfully create a JWT in a google api fashion using jwt-simple (which uses crypto and such):
var fs = require('fs')
, jwt = require('jwt-simple')
, keypath = '/path/to/your.pem'
, secret = fs.readFileSync( keypath, { encoding: 'ascii' })
, now = Date.now()
, payload = {
scope: 'https://www.googleapis.com/auth/<service>',
iss : '<iss_id>#developer.gserviceaccount.com',
aud : 'https://accounts.google.com/o/oauth2/token',
iat : now,
exp : now+3600
}
, token = jwt.encode( payload, secret, 'RS256' )
, decoded = jwt.decode( token, secret, 'RS256' );
console.log( token );
console.log( decoded );
I think this code sample is completely unsecure. If you look at the latest JWT code, it doesn't event use the secret on your decode call.
https://github.com/hokaccha/node-jwt-simple/blob/master/lib/jwt.js
It basically just decodes the second segment and returns it which means anyone could have changed the value and its not verified.
Related
I am trying to encrypt a value on my server with a private key to store it on the client within an httpOnly cookie.
I am having trouble with the encryption/decryption lifecycle
function encrypt(input) {
const encryptedData = crypto.privateEncrypt(
privateKey,
Buffer.from(input)
)
return encryptedData.toString('base64')
}
function decrypt(input) {
const decryptedData = crypto.privateDecrypt(
{ key: privateKey },
Buffer.from(input, 'base64'),
)
return decryptedData.toString()
}
const enc = encrypt('something moderately secret')
const dec = decrypt(enc)
console.log(dec) // 'something moderately secret'
However the crypto.privateDecrypt function is throwing with
Error: error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error
Side question, is it safe to reuse the same private key the server uses to sign JWTs. It's an rsa key generated using ssh-keygen -t rsa -b 4096 -m PEM -f RS256.key
So, you don't use crypto.privateEncrypt() with crypto.privateDecrypt(). That's not how they work. Those functions are for asymmetric encryption, not for symmetric encryption. You use either of these two pairs:
crypto.publicEncrypt() ==> crypto.privateDescrypt()
crypto.privateEncrypt() ==> crypto.publicDecrypt()
So, that's why you're getting the error you're getting. The nodejs doc for crypto.privateDecript() says this:
Decrypts buffer with privateKey. buffer was previously encrypted using the corresponding public key, for example using crypto.publicEncrypt().
If what you really want is symmetric encryption, there are a bunch of options in the crypto module for that. There are some examples shown here: https://www.section.io/engineering-education/data-encryption-and-decryption-in-node-js-using-crypto/ and https://fireship.io/lessons/node-crypto-examples/#symmetric-encryption-in-nodejs.
My project is building an authentication service based on .NET Core and the System.IdentityModel.Tokens.Jwt nuget package. We want to create JWT tokens that include the public key certificate (or certificate chain) that can be used to verify the JWT digital signatures. This is possible with commercial identity providers (SaaS), and is supported in the JWT specification by means of a header parameter called "x5c". But I have so far been unable to get this to work using System.IdentityModel.Tokens.Jwt.
I am able to create a JWT token signed using a certificate. The certificate is self-signed and created using openssl (commands included underneath).
My test code in C# looks like this:
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
// more usings..
public static string GenerateJwtToken(int exampleAccountId, string x509CertFilePath, string x509CertFilePassword)
{
var tokenHandler = new JwtSecurityTokenHandler();
var signingCert = new X509Certificate2(x509CertFilePath, x509CertFilePassword);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, exampleAccountId.ToString()) }),
Expires = DateTime.UtcNow.AddDays(30),
Audience = "myapp:1",
Issuer = "self",
SigningCredentials = new X509SigningCredentials(signingCert, SecurityAlgorithms.RsaSha512Signature),
Claims = new Dictionary<string, object>()
{
["test1"] = "hello world",
["test2"] = new List<int> { 1, 2, 4, 9 }
}
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
The generated token header deserializes to this in jwt.io:
{
"alg": "RS512",
"kid": "193A49ED67F22850F4A95258FF07571A985BFCBE",
"x5t": "GTpJ7WfyKFD0qVJY_wdXGphb_L4",
"typ": "JWT"
}
Thing is, I would like to get the "x5c" header parameter output as well. The reason for this is that my project is trying to include the certificate with the public key to validate the token signature inside the token itself, and "x5c" is a good way to do this. But I just cannot get this to work.
I have tried adding x5c manually with AdditionalHeaderClaims on SecurityTokenDescriptor, but it just isn't being output in the token.
Does anybody know how to do this, or can you point me to some solid resources on the subject?
By the way, this is how I generated the certificate used (on Windows):
openssl genrsa -out private2048b.key 2048
openssl req -new -key private2048b.key -out myrequest2048.csr -config <path to openssl.cfg>
openssl x509 -req -days 3650 -in myrequest2048.csr -signkey private2048b.key -out public2048b.crt
openssl pkcs12 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in public2048b.crt -inkey private2048b.key -out mypkcs2048.pfx -name "Testtest"
The PFX is the file being read and used in the code.
Update for posterity
Using Abdulrahman Falyoun's answer, the final part of the code was updated to use token.Header.Add to manually add in the "x5c" header parameter, before serializing the JWT token. Token had to be cast as JwtSecurityToken.
This worked, and created a token that was valid (and had a signature that could immediatly be verified) in https://jwt.io :
// create JwtSecurityTokenHandler and SecurityTokenDescriptor instance before here..
var exportedCertificate = Convert.ToBase64String(signingCert.Export(X509ContentType.Cert, x509CertFilePassword));
// Add x5c header parameter containing the signing certificate:
var token = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken;
token.Header.Add(JwtHeaderParameterNames.X5c, new List<string> { exportedCertificate });
return tokenHandler.WriteToken(token);
What is x5c?
The "x5c" (X.509 certificate chain) Header Parameter contains the X.509 public key certificate or certificate chain [RFC5280] corresponding to the key used to digitally sign the JWS. The certificate or certificate chain is represented as a JSON array of certificate value strings. Each string in the array is a base64-encoded (not base64url-encoded) DER [ITU.X690.2008] PKIX certificate value. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one. The recipient MUST validate the certificate chain according to RFC 5280 [RFC5280] and consider the certificate or certificate chain to be invalid if any validation failure occurs. The use of this Header Parameter is OPTIONAL.
Note
From the security point of view - do not use the x5c certificate to validate the signature directly. In that case, anybody could just provide their own certificate and spoof any identity.
The purpose of the x5t / x5t#S256 header is to identify the signer - check you trust the certificate provided by x5c or x5t#S256 (or its issuer) under the specified iss, only then you should validate the signature.
so to build the X509 chain
X509Chain chain = new X509Chain()
bool success = chain.Build(cert);
if (!success) throw Error
Then for each chain.ChainElements value, take the Certificate property RawValue property (and base64 encode it).
finally, you got the string for x5c and should only provide it to the headers of jwt.
See the following links
Create JWK Set Containing Certificates
Generate x5c certificate chain from JWK
How to obtain JWKs and use them in JWT signing?
How to get x5c from RSACryptoServiceProvider
Hope it's useful.
#Edit
If the issue was to supply the x5c to the header, you have to add it using
token.Header.Add(name, value)
I'm having a hard time authenticating a token using a x5c. (MS OAuth/Azure) Using jsonwebtoken in Rust.
Below is the code...
// Trying to isolate the problem by only checking the signature.
let validation_config = jsonwebtoken::Validation {
algorithms: vec![jsonwebtoken::Algorithm::RS256],
leeway: 0,
validate_exp: false,
validate_iat: false,
validate_nbf: false,
aud: None,
iss: None,
sub: None
};
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIn...";
let x5c_cert = "MIIDBTCCAe2gAwIBAgIQKOfEJNDyDplBSXKYcM...";
let raw_der = base64::decode_config(der, base64::STANDARD).unwrap();
let d = jsonwebtoken::decode::<MsOAuthPayload>(&token, &raw_der, &validation_config);
The above always returns InvalidSignature.
RS265 is the correct algo.
The cert is correct. I tried it on jwt.io by adding a BEGIN/END cert and it validates fine.
I used openssl to convert the BEGIN/END pem to DER and the bytes match up from the base 64 decode.
The key URL is: https://login.microsoftonline.com/common/discovery/v2.0/keys but my specific tenant returns the same keys.
Anyone have some insight on what I'm doing wrong here?
Thanks
EDIT: Minimal working example here. https://pastebin.com/RqqRaKkU
In short : the token generated using below code gives the correct headers and payload(when I paste the generated token in JWT.io).
It only works when I insert the secret and press the secret encoded checkbox in jwt.io. After that I get valid token.
But var token = jwt.sign(payload, privateKEY, signOptions); this step should do the same thing I guess.
My code.
var jwt = require('jsonwebtoken');
var payload = {
"userId" : 'YYYYYYYYYYYYYYYYYYYYYYY',
"iat" : new Date().getTime(),
};
var signOptions = {
algorithm: "HS512"
};
var privateKEY = 'XXXXXXXXXXXXXXXXXXXXXXXX';
var token = jwt.sign(payload, privateKEY, signOptions);
console.log("Token :" + token);
This gives me an invalid token but when i paste that token in jwt.io I get the correct Headers and Payload.
And if I insert my secret and press the checkbox I get the correct token.
What I am I doing wrong. Thanks
When you check the checkbox on jwt.io, it base64 decodes your secret. Since you don't base64 encode your secret in your code, you shouldn't check that box on jwt.io. Both tokens are correct, but for different secrets. If you want the same token that you got from jwt.io with the box checked, you can use this:
var decodedSecret = Buffer.from(privateKEY, 'base64');
Then use that to sign your token instead of privateKEY. However, this doesn't really make sense, as your key isn't base64 encoded to begin with.
I have an encrypted SAML 2.0 response/assertion that i need to decrypt. the format looks like this:
<saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xenc:EncryptedKey>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
<xenc:CipherData>
<xenc:CipherValue>{some ciphers}</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>{assertion body}</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</saml:EncryptedAssertion>
I also have a private decryption key with format like this:
-----BEGIN RSA PRIVATE KEY-----
{mumbo jumbos}
-----END RSA PRIVATE KEY-----
I have tried using OneLogin's saml decryption tools here to decrypt the encrypted SAML assertion (copy+paste to that input box), and it works like a charm.
https://www.samltool.com/decrypt.php
However, when I tried to use nodejs, passport-saml to import the private key file and try to decrypt the response, it gets a "Invalid PEM formated" if i omit the ("-----BEGIN----" or "---END---" banner), or it gets a "Invalid RSAES-OAEP padding" error.
This is my code snippet:
const fs = require('fs');
const Promise = require('bluebird');
const path = require('path');
const forge = require('node-forge');
let pkey = fs.readFileSync(path.join(__dirname,'./myTestKey.pem'), 'utf8');
//let testKey = new Buffer(pkey).toString('base64');
let SAML = require('passport-saml/lib/passport-saml/saml.js');
let saml = new SAML({...,decryptionPvk: pkey });
let validatePostResponseAsync = Promise.promisify(saml.validatePostResponse);
validatePostResponseAsync(myResponse, pkey)
.then(response=>{
})
.catch(error=>{
// it always throw error of the 2 mentioned above.
})
Any workaround would be appreciated.
I think i figured it out. for those who is struggling with similar issue, you must include the ---BEGIN RSA PRIVATE KEY--- and ---END RSA PRIVATE KEY---. the node-forge library which passport-saml lib is depending on will throw error if the banner is not included.