How can I sign a JWT with RSA SHA256 in an Azure API Management Policy Expression? - azure

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())
{
....
}

Related

Azure "JsonWebTokenError: invalid algorithm"

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.

Exception Error while reading from Azure Key Vault

We faced the below exception while trying to read a secret from Azure Key Vault from Service fabric application.
The application uses Client Certificate to authenticate with AAD and access the KeyVault to fetch the secret.
This issue is occurring intermittently.
Is there a way we could identify the root cause so that the same error can be prevented from further occurrences.
Message: AADSTS70002: Error validating credentials. AADSTS50012: Client assertion is not within its valid time range.
Trace ID: 333ee9c1-c74f-432d-824a-000f38a0e400
Correlation ID: 35b5cadf-c538-4f75-b1fb-56c4743088f4
Timestamp: 2018-10-24 06:23:30Z
......
Client assertion is not within its valid time range.
According to your error message and your issue occurs intermittently, I think it may be your token's region time cause the problem. Region time may have some time interval with your token valid time.
So, I suggest that you could use DateTime.UtcNow as standard to set your token start time and end time. Here is a code sample you could refer to.
private static async Task<string> GetClientAssertiotokenAsync(string tenantId,string clientId)
{
X509Certificate2 cert = new X509Certificate2(#"D:\Joey\Documents\joey.pfx", "password", X509KeyStorageFlags.MachineKeySet);
var now = DateTime.UtcNow;
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Audience = $"https://login.microsoftonline.com/{tenantId}/oauth2/token",
Issuer = clientId,
NotBefore = now.AddHours(1),
Expires = now.AddHours(3),
Subject = new ClaimsIdentity(new[] {
new Claim("sub",clientId)}),
SigningCredentials = new X509SigningCredentials(cert)
};
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
string tokenString = tokenHandler.WriteToken(token);
}
For more details, you could refer to this article.

send message to Azure service bus by Azure scheduler using post

i want to send message to Azure service bus by Azure scheduler using post
like demo in this page
http://www.prasadthinks.com/
but i don't know how to set 'authorization' property in Http Header.
As far as I know, the 'authorization' property must contains the service bus's access token.
You could use your shared access policies's key-name and key to generate the access token by using codes.
More details, you could refer to below codes.
string keyName = "keyname";
string key = "key";
var sasToken = createToken("http://yourservicebusname.servicebus.windows.net/queuename", keyName, key);
createToken function:
private static string createToken(string resourceUri, string keyName, string key)
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 7200); //EXPIRES in 2h
string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
//this is the auth token
var sasToken = String.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
return sasToken;
}
The result is like below:
This is the 'authorization' property, you could copy it.But this token has two hours limit.
The azure scheduler job setting like below:
Besides, the azure scheduler job have already support send the message to the service bus, you don't need to create the sas token by yourself, you could just add the keyName and key in its authentication settings.
More details, you could refer to below images:

Send a GCM Native Notification through REST in Azure notification hub

I am trying to call a Azure Notification Hub REST API , based on this documentation. As they said , I tried to create a Header of API and it giving me an error "The credentials contained in the authorization header are not in the WRAP format".
My Demo DefaultFullSharedAccessSignature is :
Endpoint=sb://shinetrialhub-ns.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=BaGJbFDQZ+hkbi2MdUj7gU0tOM+aC/k+mez9J/y54Qc=
Here my API: https://shinetrialhub-ns.servicebus.windows.net/shinetrialhub/messages/?api-version=2015-01
by adding valid header (please see the MSDN document)
You need to generate Shared Access Signature Authentication with Service Bus. I've been using code below to achieve this:
resourceUri: https://shinetrialhub-ns.servicebus.windows.net/shinetrialhub/
keyName: RootManageSharedAccessKey
key: the value for RootManageSharedAccessKey
private string GetSasToken(string resourceUri, string keyName, string key)
{
var expiry = GetExpiry();
var stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = string.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
return sasToken;
}
private string GetExpiry()
{
var sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
return Convert.ToString((int) sinceEpoch.TotalSeconds + 102000); //token valid for that many seconds
}
Also make sure you have all the right headers, as shown in the documentation.

WIF SAML RequestSecurityToken STS Internal server error

I try to reach my STS to request a token. The code is based on a blog post by #leastprivilege : WCF and Identity in .NET 4.5: External Authentication with WS-Trust. I use the explicit approach (by code).
private static SecurityToken RequestSecurityToken()
{
// set up the ws-trust channel factory
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(
SecurityMode.TransportWithMessageCredential),
"https://federation.mydomain/adfs/services/trust/mex") { TrustVersion = TrustVersion.WSTrust13 };
//factory.Credentials.SupportInteractive = false;
factory.Credentials.UserName.UserName = "user-pcote";
factory.Credentials.UserName.Password = "123456";
// create token request
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
AppliesTo = new EndpointReference("https://myRP/")
};
var channel = factory.CreateChannel();
return channel.Issue(rst);
}
I can see the XML when copying the STS endpoint adress in my browser, therefore the federation server is reachable. But I always get an internal server error (500) as soon as I issue the token request. Does anybody have an idea what could be my problem here.
Finally managed to get it working by changing the KeyType to KeyTypes.Bearer (since there's no certificate applied to the RP in AD FS). I based myseflf on this website that gives a good explanations on how it all relates :
http://blog.skadefro.dk/2011/09/claimsbased-authentication-and-wcf.html
if we look in Microsoft.IdentityModel.SecurityTokenService.KeyTypes we
see we can use Asymmetric, Symmetric or Bearer. Tons of post out there
about this.
If you use Asymmetric you as requestor need to supply a key to encrypt
the claims with. ( set "UseKey” )
If you use Symmetric the identity provider have all ready been told
what certificate to use, to encrypt the claims with.
If you choose Bearer. The token get signed, but claims will not be
encrypted. If a token signing certificate have been assigned on the
Relying Party, claims will simply not be included at all.
When you request a token, the token gets signed (not encrypted) with a
certificate installed on the Identity Provider ( ADFS ). If you add a
certificate on a Relying Party Trust (RP) on the ADFS server, the
claims inside the token gets encrypted with with that certificate.
Only host/applications that have access to the private key of that
certificate can now decrypt the token and read the claims. You don’t
need to read the claims in order to authenticate your self. For
instance if you have a WCF Service you want to call from within an
application. You can from within that application still request a
token from the ADFS server and then access the WCF service with that
Token. As long as the WCF service have access to the private key and
can read the claims, your application don’t need it.
private static SecurityToken RequestSecurityToken()
{
var binding = new UserNameWSTrustBinding(
SecurityMode.TransportWithMessageCredential);
var factory = new WSTrustChannelFactory(
binding,
new EndpointAddress(new Uri("<your_adfs_uri>/adfs/services/trust/13/usernamemixed"), EndpointIdentity.CreateSpnIdentity("host/your.spn.com"))) { TrustVersion = TrustVersion.WSTrust13 };
factory.Credentials.UserName.UserName = "username";
factory.Credentials.UserName.Password = "password";
// create token request
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
AppliesTo = new EndpointReference(<uri_of_your_relying_party>)
};
var channel = factory.CreateChannel();
try
{
var response = channel.Issue(rst);
return response ;
}
catch (Exception e)
{
var message = e.Message;
return null;
}
}
I managed to find the right endpoint (which was /adfs/services/trust/13/usernamemixed) but now I get the following error :
ID4007: The symmetric key inside the requested security token must be encrypted

Resources