Azure Static Web App (SWA) with integrated API. One of the step at backend API is to validate the Bearer Token with public key submitted in request headers:
const jwt = require("jsonwebtoken"); // v 8.5.1
async function getMSPublicKey(misc) // misc contains kid and tenantId, confirmed in F12 request header
{
var vurl = "https://login.microsoftonline.com/" + misc.tenantId + "/v2.0/.well-known/openid-configuration";
const x1 = await fetch(vurl);
const x2 = await x1.json();
const x3 = await fetch(x2.jwks_uri);
const k = await x3.json();
return pkey = k.keys.find( k => k.kid === misc.kid).x5c[0]; // public key in the entry matching kid
}
var vmisc = JSON.parse(ac.req.headers["misc"]);
var publickey = "-----BEGIN CERTIFICATE-----\n" + await getMSPublicKey(vmisc) + "\n-----END CERTIFICATE-----";
// next line is reported in AppTraces, Message = JsonWebTokenError: invalid algorithm
var payload = jwt.verify(theToken, publickey, { algorithms: ['RS256'] });
// theToken is validated ok at jwt.io
It only occurs when deployed to Azure cloud, local Azure Static Web Apps emulator is all ok.
Update, Guess this is something about Azure cloud, particularly security. similar result on another package Jose, error only on Azure cloud.
Update: found culprit My original code was sending the token in under Authorization name. Azure log shows its read-in length is always 372 vs. 1239 tested in local emulator. After renaming it to something else like mytoken, all good! This is undocumented, reminder to everyone: avoid sensitive/reserved words.
This ought to be painless and work the same with less code on your end, it handles rotation, re-fetching of the public keys, as well as implements a complete applicable JWK selection algorithm for all known JWS algorithms. Also does not depend on a brittle x5c[0] JWK parameter.
const jose = require('jose')
const JWKS = jose.createRemoteJWKSet(new URL(`https://login.microsoftonline.com/${misc.tenantId}/discovery/v2.0/keys`))
// JWKS you keep around for subsequent verifications.
const { payload, protectedHeader } = await jose.jwtVerify(jwt, JWKS)
Please check if the below steps help to work around:
Replace the CERTIFICATE keyword with PUBLIC KEY if you're using the public key or PRIVATE KEY if you're using the Private Key or RSA PRIVATE KEY if you are using RSA Private Key.
Also, the problem again occurs in the way we format the Public Key which requires begin and end lines, and line breaks at every 64 characters.
Refer here for more information.
Related
I have difficulty manipulating the Jose Node.JS documentation to chain the creation of a JWS and JWE. I cannot find the proper constructor for encryption. It looks like I can only encrypt a basic payload not a signed JWS.
Here is the code sample I try to fix to get something that would look like
const jws = await createJWS("myUserId");
const jwe = await encryptAsJWE(jws);
with the following methods
export const createJWS = async (userId) => {
const payload = {
}
payload['urn:userId'] = userId
// importing key from base64 encrypted secret key for signing...
const secretPkcs8Base64 = process.env.SMART_PRIVATE_KEY
const key = new NodeRSA()
key.importKey(Buffer.from(secretPkcs8Base64, 'base64'), 'pkcs8-private-der')
const privateKey = key.exportKey('pkcs8')
const ecPrivateKey = await jose.importPKCS8(privateKey, 'ES256')
const assertion = await new jose.SignJWT(payload)
.setProtectedHeader({ alg: 'RS256' })
.setIssuer('demolive')
.setExpirationTime('5m')
.sign(ecPrivateKey)
return assertion
}
export const encryptAsJWE = async (jws) => {
// importing key similar to createJWS key import
const idzPublicKey = process.env.IDZ_PUBLIC_KEY //my public key for encryption
...
const pkcs8PublicKey = await jose.importSPKI(..., 'ES256')
// how to pass a signed JWS as parameter?
const jwe = await new jose.CompactEncrypt(jws)
.encrypt(pkcs8PublicKey)
return jwe
}
The input to the CompactEncrypt constructor needs to be a Uint8Array, so just wrapping the jws like so (new TextEncoder().encode(jws)) will allow you to move forward.
Moving forward then:
You are also missing the JWE protected header, given you likely use an EC key (based on the rest of your code) you should a) choose an appropriate EC-based JWE Key Management Algorithm (e.g. ECDH-ES) and put that as the public key import algorithm, then proceed to call .setProtectedHeader({ alg: 'ECDH-ES', enc: 'A128CBC-HS256' }) on the constructed object before calling encrypt.
Here's a full working example https://github.com/panva/jose/issues/112#issue-746919790 using a different combination of algorithms but it out to help you get the gist of it.
In an Azure API Management Policy Expression I need to create a JWT signed with a private key.
When I try to use RSACryptoServiceProvider - just to check whether this feedback already got resolved - I get this error when trying to save the policy:
Usage of type 'System.Security.Cryptography.RSACryptoServiceProvider' is not supported within expressions
Following a hint from maxim-kim, I tried RSA.Create() and to convert from this tutorial
var privateKey = "whatever";
RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(privateKey, out _);
var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256)
{
CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
};
var now = DateTime.Now;
var unixTimeSeconds = new DateTimeOffset(now).ToUnixTimeSeconds();
var jwt = new JwtSecurityToken(
audience: _settings.Audience,
issuer: _settings.Issuer,
claims: new Claim[] {
new Claim(JwtRegisteredClaimNames.Iat, unixTimeSeconds.ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(nameof(claims.FirstName), claims.FirstName),
new Claim(nameof(claims.LastName), claims.LastName),
new Claim(nameof(claims.Email), claims.Email)
},
notBefore: now,
expires: now.AddMinutes(30),
signingCredentials: signingCredentials
);
string token = new JwtSecurityTokenHandler().WriteToken(jwt);
return new JwtResponse
{
Token = token,
ExpiresAt = unixTimeSeconds,
};
but got the next error:
'RSA' does not contain a definition for 'ImportRSAPrivateKey' and no extension method 'ImportRSAPrivateKey' accepting a first argument of type 'RSA' could be found (are you missing a using directive or an assembly reference?)
So my question: Is there a way to create a signed JWT in an Azure API Management Policy Expression?
Thanks to this and other articles, I managed to sign in an APIM policy. Therefore I would like to share this.
<set-variable name="signedPayload" value="#{
using (RSA rsa = context.Deployment.Certificates["thumbprint"].GetRSAPrivateKey())
{
long unixTimeStampInSeconds = DateTimeOffset.Now.ToUnixTimeSeconds();
string header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
string claimset = String.Format("{{ \"scope\": \"https://www.googleapis.com/auth/devstorage.read_write\", \"aud\": \"https://oauth2.googleapis.com/token\", \"iss\": \"blahblah.gserviceaccount.com\", \"iat\": {0}, \"exp\": {1} }}", unixTimeStampInSeconds, unixTimeStampInSeconds + 3599);
string payload = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(header)) + "." + System.Convert.ToBase64String(Encoding.UTF8.GetBytes(claimset));
byte[] signature = rsa.SignData(Encoding.UTF8.GetBytes(payload), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return System.Net.WebUtility.UrlEncode(payload + "." + System.Convert.ToBase64String(signature));
}
}" />
RSA initialization based on dynamically resolved private and public keys is not supported today.
If RSA parameters are not request specific you can upload x509 certificate to APIM containing required RSA parameters and use it within expressions:
using (var rsa = context.Deployment.Certificates["thumbprint"].GetRSAPrivateKey())
{
....
}
I need to make sure a client generated RSA key pair matches before signing it. I can't seem to find any documentation (npm:node-forge) on how to do so. I'm guessing I could sign something with it, and then verify the signature, but that's not efficient. I currently have this:
const Forge = require("node-forge");
try {
publicKey = Forge.pki.publicKeyFromPem(publicKey);
privateKey = Forge.pki.privateKeyFromPem(privateKey);
} catch(err) {
// ...
}
// ...
Any ideas are appreciated.
I've found my answer: I don't need to be sent the public key in the first place. You can build the public key from the private key like this:
// const privateKey = ...;
const publicKey = Forge.pki.setRsaPublicKey(privateKey.n, privateKey.e);
More information on this solution can be found here: Extract public key from private key pem using only nodejs/javascript.
I've seen example of signing https://www.pdftron.com/documentation/nodejs/guides/features/signature/sign-pdf
signOnNextSave uses PKCS #12 certificate, but I use Google KMS for asymmetric signing to keep private keys safe.
Here is example of signing and verifying by Google Cloud KMS
I tried to implement custom SignatureHandler but Node.JS API is different from Java or .NET
https://www.pdftron.com/api/pdfnet-node/PDFNet.SignatureHandler.html
How can I implement custom signing and verifying logic?
const data = Buffer.from('pdf data')
// We have 2048 Bit RSA - PSS Padding - SHA256 Digest key in Google Cloud KMS
const signAsymmetric = async () => {
const hash = crypto.createHash('sha256')
hash.update(data)
const digest = hash.digest()
const digestCrc32c = crc32c.calculate(digest)
// Sign the data with Cloud KMS
const [signResponse] = await client.asymmetricSign({
name: locationName,
digest: {
sha256: digest
},
digestCrc32c: {
value: digestCrc32c
}
})
if (signResponse.name !== locationName) {
throw new Error('AsymmetricSign: request corrupted in-transit')
}
if (!signResponse.verifiedDigestCrc32c) {
throw new Error('AsymmetricSign: request corrupted in-transit')
}
if (
crc32c.calculate(signResponse.signature) !==
Number(signResponse.signatureCrc32c.value)
) {
throw new Error('AsymmetricSign: response corrupted in-transit')
}
// Returns signature which is buffer
const encoded = signResponse.signature.toString('base64')
console.log(`Signature: ${encoded}`)
return signResponse.signature
}
// Verify data with public key
const verifyAsymmetricSignatureRsa = async () => {
const signatureBuffer = await signAsymmetric()
const publicKeyPem = await getPublicKey()
const verify = crypto.createVerify('sha256')
verify.update(data)
verify.end()
const key = {
key: publicKeyPem,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING
}
// Verify the signature using the public key
const verified = verify.verify(key, signatureBuffer)
return verified
}
At this time, the PDFTron SDK only supports custom handlers on C++, Java, and C# (there are more plans to include additional languages in the future).
On a different platform like C++, you would extend the custom handler functions by putting hash.update(data) into SignatureHandler::AppendData, and the rest of signAsymmetric would go into SignatureHandler::CreateSignature. A name would be given to the custom handler for interoperability like Adobe.PPKLite (we do not yet support custom handler SubFilter entries, only Filter -- see PDF standard for the difference -- but this won't matter so long as you use a verification tool that supports Filter Adobe.PPKLite). Please see the following link for a concrete example:
https://www.pdftron.com/documentation/samples/cpp/DigitalSignaturesTest
As for verification, our code can already do this for you if your signatures fulfill the following conditions:
they use a standard digest algorithm
they use RSA to sign
they use the correct data formats according to the PDF standard (i.e. detached CMS, digital signature dictionary)
If you have more questions or require more details, please feel free to reach out to PDFTron support at support#pdftron.com
I am new to OAuth and I used this tutorial to generate access token from client app to target app. The code itself is working fine, but the access token I generated has invalid signature when I decoded on https://jwt.io/
Here's the code from the tutorial
public class ServicePrincipal
{
/// <summary>
/// The variables below are standard Azure AD terms from our various samples
/// We set these in the Azure Portal for this app for security and to make it easy to change (you can reuse this code in other apps this way)
/// You can name each of these what you want as long as you keep all of this straight
/// </summary>
static string authority = ""; // the AD Authority used for login. For example: https://login.microsoftonline.com/myadnamehere.onmicrosoft.com
static string clientId = ""; // client app's client id
static string clientSecret = ""; // client app's secret key
static string resource = ""; // target app's App ID URL
/// <summary>
/// wrapper that passes the above variables
/// </summary>
/// <returns></returns>
static public async Task<AuthenticationResult> GetS2SAccessTokenForProdMSAAsync()
{
return await GetS2SAccessToken(authority, resource, clientId, clientSecret);
}
static async Task<AuthenticationResult> GetS2SAccessToken(string authority, string resource, string clientId, string clientSecret)
{
var clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext context = new AuthenticationContext(authority, false);
AuthenticationResult authenticationResult = await context.AcquireTokenAsync(
resource, // the resource (app) we are going to access with the token
clientCredential); // the client credentials
return authenticationResult;
}
}
There is another piece of code I found that can also generate the access token:
AuthenticationContext authenticationContext =
new AuthenticationContext({authority});
ClientCredential clientCredential = new ClientCredential({client app id}, {client app secret});
try
{
AuthenticationResult result =
await authenticationContext.AcquireTokenAsync({target app's App ID URL},
clientCredential);
}
catch (Exception e)
{
return false;
}
Both of the code gave me invalid signature access token with version 1.0
There are two issues here:
I noticed is that when I decode the access token, it shows "ver": "1.0". Does it mean it is using OAuth1.0? Because I suppose to use OAuth 2.0.. Why would the code generate token that create OAuth1.0 not OAuth2.0?
Why would it be invalid signature?
I tried the same code with yours, got the same situation invalid signature, but when I changed the jwt ALGORITHM to HS256, I got the Signature Verified.
And the signature:
And the differences about RRS256 and HS256:
RS256 (RSA Signature with SHA-256) is an asymmetric algorithm, and it uses a public/private key pair: the identity provider has a private (secret) key used to generate the signature, and the consumer of the JWT gets a public key to validate the signature. Since the public key, as opposed to the private key, doesn't need to be kept secured, most identity providers make it easily available for consumers to obtain and use (usually through a metadata URL).
HS256 (HMAC with SHA-256), on the other hand, is a symmetric algorithm, with only one (secret) key that is shared between the two parties. Since the same key is used both to generate the signature and to validate it, care must be taken to ensure that the key is not compromised.
Are you posting your key into the form at jwt.io? Try to make a real rest call using the token in the authorization header. If everything is working and jwt isn't, maybe it's on them.
I noticed is that when I decode the access token, it shows "ver": "1.0". Does it mean it is using OAuth1.0? Because I suppose to use OAuth 2.0.. Why would the code generate token that create OAuth1.0 not OAuth2.0?
You are using OAuth2.0 , the ver:"1.0" means the JWT token is issued by Azure AD V1.0 Endpoint .
Why would it be invalid signature?
The API needs to check if the algorithm, as specified by the JWT header (property alg), matches the one expected by the API . AAD use RS256 , so you should change to RS256:
The normal way is to build from modulus and exponent , finding them from https://login.microsoftonline.com/common/discovery/keys matching kid and x5t from the token . Any use online tool like https://play.golang.org/ to get public key consists of two components: n and e. You can also use x5c value , click here for samples .