i want to us Key Vault key to create JWT token and then validate it.
Im using this code:
public static async Task<string> SignJwt()
{
var tokenHandler = new JwtSecurityTokenHandler();
var signinKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", "1") }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(signinKey, SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
and it works fine. I was googling a lot and found this snippet for SigningCredentials using Identity extension nuget:
new SigningCredentials(new KeyVaultSecurityKey("https://myvault.vault.azure.net/keys/mykey/keyid", new KeyVaultSecurityKey.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)), "RS256")
{
CryptoProviderFactory = new CryptoProviderFactory() { CustomCryptoProvider = new KeyVaultCryptoProvider() }
});
But it is not clear for me, what really AuthenticationCallback is and how to implement that and if i will be able to use that in Azure in web app or azure function?
Firstly, a JWT token consists of 3 parts (Header, Payload and Signature) and all those 3 parts are Base64UriEncoded.
To get the Signature you need to generate header and payload, then combine them by dot.**
Below is the sample code to verify JWT using Azure kay Vault.
const key = await this.keyClient.getKey(this.KEY_NAME);
const cryptClient = new CryptographyClient(key, new DefaultAzureCredential());
const util =require('util')
const base64 = require('base64url');
const JWT=""
const jwtHeader = JWT.split('.')[0];
const jwtPayload = JWT.split('.')[1];
const jwtSignature = JWT.split('.')[2];
const signature = base64.toBuffer(jwtSignature)
const data = util.format('%s.%s', jwtHeader, jwtPayload);
const hash = crypto.createHash('sha256');
const digest = hash.update(data).digest()
const verified =await cryptClient.verify("RS256",digest,signature)
Here are few SO threads with related discussions. SO1, SO2 and SO3
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.
I'm trying to generate a common secret key using ConcatKDF algorithm.
In the following example alice is trying to generate a common secret using bobs public key using node-jose library:
const index = require('node-jose/lib/algorithms/index');
const keystore = jose.JWK.createKeyStore();
const bobKey = await keystore.generate('EC', 'P-256');
const aliceKey = await keystore.generate('EC', 'P-256');
const bobPublicKey = bobKey.toJSON();
const alicPrivateKey = aliceKey.toJSON(true);
const props = {
public: bobPublicKey,
length: 256
};
const result = await index.derive('ECDH-CONCAT', alicPrivateKey, props);
It fails with the following error:
Error: invalid EC public key
at validatePublic (node_modules\node-jose\lib\algorithms\ecdh.js:47:29)
at nodejs (node_modules\node-jose\lib\algorithms\ecdh.js:164:13)
at Object.main [as derive] (node_modules\node-jose\lib\algorithms\helpers.js:110:42)
at fn (node_modules\node-jose\lib\algorithms\ecdh.js:207:29)
at Object.exports.derive (node_modules\node-jose\lib\algorithms\index.js:73:10)
at Context.<anonymous> (test\unit\security\ecdh-test.js:108:32)
Could someone please let me know if I'm missing something here?
Thanks
I am using Microsoft Azure API Management service and want to use the REST API service. In creating my SAS token, which is needed otherwise the API call doesn't authorize, I'm having difficulty forming a proper token. Microsoft's webpage about this SAS token for API Management only shows an example in C#. I want to know how to form an SAS token in Node.js, which is not shown. Below is my code that was working last week, but is not now for some unknown reason. The error I get is: 401 Authorization error, token invalid
If someone can help me formulate this token, I would appreciate it.
This is Microsoft's webpage regarding this authentication token: https://learn.microsoft.com/en-us/rest/api/apimanagement/apimanagementrest/azure-api-management-rest-api-authentication
Here's my code:
const crypto = require('crypto');
const util = require('util');
const sign = () => {
const id = ${process.env.id}
const key = `${process.env.SASKey}`;
const date = new Date();
const newDate = new Date(date.setTime(date.getTime() + 8 * 86400000));
const expiry = `${newDate.getFullYear()}${
newDate.getMonth() < 10
? '' + newDate.getMonth() + 1
: newDate.getMonth() + 1
}${newDate.getDate()}${newDate.getHours()}${
newDate.getMinutes() < 10
? '0' + newDate.getMinutes()
: newDate.getMinutes()
}`;
const dataToSignString = '%s\n%s';
const dataToSign = util.format(dataToSignString, ${id}, expiry);
const hash = crypto
.createHmac('sha512', key)
.update(dataToSign)
.digest('base64');
const encodedToken = `SharedAccessSignature ${id}&${expiry}&${hash}`;
console.log(encodedToken);
return encodedToken;
};
Try the code:
protected getAPIManagementSAS(){
let utf8 = require("utf8")
let crypto= require("crypto")
let identifier = process.env.API_IDENTIFIER;
let key = process.env.API_KEY;
var now = new Date;
var utcDate = new Date(now.getUTCFullYear(),now.getUTCMonth(), now.getUTCDate() , now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds(), now.getUTCMilliseconds());
let expiry = addMinutes(utcDate,1,"yyyy-MM-ddThh:mm:ss") + '.0000000Z'
var dataToSign = identifier + "\n" + expiry;
var signatureUTF8 = utf8.encode(key);
var signature = crypto.createHmac('sha512', signatureUTF8).update(dataToSign).digest('base64');
var encodedToken = `SharedAccessSignature uid=${identifier}&ex=${expiry}&sn=${signature}`;
return encodedToken
}
For more information, see here.
After a million tries, it seems like the only format acceptable right now is:
SharedAccessSignature uid=${identifier}&ex=${expiry}&sn=${signature}
If you are using the other format that has the "integration" parameter, that's a hit or a miss, mostly miss though. Set the uid as "integration" if that's your identifier and follow the above format as it works.
I have implemented Key Vault access token generator using below codebase:
private async Task<string> GetStaticToken(string authority, string resource)
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(_appSettings.ClientId, _appSettings.ClientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, credential);
return result.AccessToken;
}
I know how to use this token into Authorization header and get the secret values using Rest API call. But can we use the same AccessToken string into below code base:
var builder = new ConfigurationBuilder();
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
builder.AddAzureKeyVault($"https://{myVaultName}.vault.azure.net/", keyVaultClient, new DefaultKeyVaultSecretManager());
Configuration = builder.Build();
Here is it possible to re-use AccessToken string value, while creating KeyVaultClient? Something like below:
var tokenValue = GetStaticToken (authority, resource);
var keyVaultClient = new KeyVaultClient(tokenValue);
Basically I would like to generate token at once and reuse string everywhere, even outside my application scope.
Note: I am aware that token will come with expiration time duration. That time GetToken will be called again.
Well, you can make a callback that returns that token:
var kvClient = new KeyVaultClient((authority, resource, scope) => Task.FromResult(tokenValue));
This simply replaces the call to get a token with an already completed Task with the token in it.
To read an Application setting in Azure function I can do
Environment.GetEnvironmentVariable("MyVariable", EnvironmentVariableTarget.Process);
Is it possible to get a Host key in a similar way? I like to identify the caller of my azure function based on the key they are using but hate to have a copy of this key in Application settings
You could install Microsoft.Azure.Management.ResourceManager.Fluent and Microsoft.Azure.Management.Fluent to do that easily.
The following is the demo that how to get kudu credentials and run Key management API .I test it locally, it works correctly on my side.
For more detail, you could refer to this SO thread with C# code or use powershell to get it.
string clientId = "client id";
string secret = "secret key";
string tenant = "tenant id";
var functionName ="functionName";
var webFunctionAppName = "functionApp name";
string resourceGroup = "resource group name";
var credentials = new AzureCredentials(new ServicePrincipalLoginInformation { ClientId = clientId, ClientSecret = secret}, tenant, AzureEnvironment.AzureGlobalCloud);
var azure = Azure
.Configure()
.Authenticate(credentials)
.WithDefaultSubscription();
var webFunctionApp = azure.AppServices.FunctionApps.GetByResourceGroup(resourceGroup, webFunctionAppName);
var ftpUsername = webFunctionApp.GetPublishingProfile().FtpUsername;
var username = ftpUsername.Split('\\').ToList()[1];
var password = webFunctionApp.GetPublishingProfile().FtpPassword;
var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}"));
var apiUrl = new Uri($"https://{webFunctionAppName}.scm.azurewebsites.net/api");
var siteUrl = new Uri($"https://{webFunctionAppName}.azurewebsites.net");
string JWT;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Basic {base64Auth}");
var result = client.GetAsync($"{apiUrl}/functions/admin/token").Result;
JWT = result.Content.ReadAsStringAsync().Result.Trim('"'); //get JWT for call funtion key
}
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + JWT);
var key = client.GetAsync($"{siteUrl}/admin/functions/{functionName}/keys").Result.Content.ReadAsStringAsync().Result;
}
The output: