RSA KeyPair public key import not working - authentication

I encounter weird problem with RSA KeyPairs in IIS. I have an application which generates signed JWT tokens using private key. I exported public key and I want to use it in another application which consumes these JWT tokens (for JWT Bearer authentication).
Technically there is an javascript client which obtains JWT token from first applications WebAPI backend and use it when authenticates to second WebAPI backend.
This code is used to load public key and setup authentication (uses OWIN startup):
// USE from container
string KEY_CONTAINER_NAME = "MyContainer";
int KEY_SIZE = 2048;
CspParameters cspParams = new CspParameters();
cspParams.Flags |= CspProviderFlags.UseMachineKeyStore;
cspParams.KeyContainerName = KEY_CONTAINER_NAME;
var rsa = new RSACryptoServiceProvider(KEY_SIZE, cspParams);
// Api controllers with an [Authorize] attribute will be validated with JWT
appBuilder.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { "MyAudience" },
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningToken = new System.IdentityModel.Tokens.RsaSecurityToken(rsa),
ValidAudience = "MyAudience",
ValidIssuer = "MyIssuer"
}
});
This command I used to import public key to store:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319>.\aspnet_regiis.exe -pi MyContainer C:\PublicKey.xml
This is not working. Public key is loaded and used but never verifies incoming tokens. However, when I load the key right from XML file it works! So I presume the exported XML file is OK. I have no idea why this is happening? Then I tried to export imported public key and compare content of these files. It differs!
This command I used to export imported public key from store (for file content comparement):
C:\Windows\Microsoft.NET\Framework64\v4.0.30319>.\aspnet_regiis.exe -px MyContainer C:\PublicKey_exp.xml
When I load public key from file, not from the store, it works fine:
StreamReader sr = new StreamReader("C:\\PublicKey.xml");
String xml = sr.ReadToEnd();
sr.Close();
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(xml);

Related

vaultsharp tls auth failed - client certificate must be supplied

Vaultsharp is not able to authenticate with vault for TLS AUTH method
C# code on windows 10, cert and key in personal store
environment windows
X509Certificate2 clientCertificate = null;
X509Store store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificateList =
store.Certificates.Find(X509FindType.FindBySubjectName, "subject name", false);
if (certificateList.Count > 0)
{
clientCertificate = certificateList[0];
};
store.Close();
// got clientCertificate here, it has private key as well
try
{
IAuthMethodInfo authMethod = new CertAuthMethodInfo(clientCertificate);
var vaultClientSettings = new VaultClientSettings("endpoint:8200", authMethod);
IVaultClient vaultClient = new VaultClient(vaultClientSettings);
Secret<Dictionary<string, object>> secret = null;
Task.Run(async () =>
{
secret = await vaultClient.V1.Secrets.KeyValue.V1.ReadSecretAsync("dummy_app/dev/connection_strings");
}).GetAwaiter().GetResult();
Above code is throwing error
{"errors":["client certificate must be supplied"]}
It should return the secret instead of throwing exception
Please check the following.
That the certificate really has a private key. (HasPrivateKey check on the object) Typically you read a private key from a store using a passphrase. I don't see that above, so it maybe that what you have is a public key.
Please ensure that the certificate is a valid cert with the full chain. The Vault API (not VaultSharp) throws an error if it cannot find the parent chain.
Please inspect the http or tcp connection to see if the cert is truly attached.

No Session handle to Renew Token in Xero-Partner-App

Error from Xero :
Token does not match an expected REQUEST token
The issue seems to be related to the comment from the Xero help site?
This error will also occur if the session handle is not used in the
access token renewal process.
I have confirmed I do not get this on the return object of the following :
public async Task<AccessTokenDto> Authorise(string oauth_token, string oauth_verifier, string org)
{
var xeroacessToken =
_authenticator.RetrieveAndStoreAccessToken(_user.Name,
oauth_token, oauth_verifier, org);
}
I am not sure how I get the session handle and then how to use this in my service call to renew the token? As per the example I was expecting this on my token.
oauth_session_handle=ODJHMGEZNGVKMGM1NDA1NZG3ZWIWNJ
"Session Handle used to renew the access token"
Code that works the first time with the token that has been retrieved from the database:
var tokenStore = new MemoryTokenStore();
tokenStore.Add(xerotoken);
var api = new RA.Xero.Partner.Core(tokenStore, XeroUser(UserId)), _hostingEnvironment)
{
UserAgent = "My Partner App " + input.ConsumerKey,
};
I tried to see if using the Partner Authentication directly would work :
var tokenStore = new MemoryTokenStore();
tokenStore.Add(xerotoken);
Settings ApplicationSettings = new Settings();
X509Certificate2 certificate = RA.Xero.Partner.Core.Certificate(_hostingEnvironment);
var partnerAuthentication = new RA.Xero.Public.PartnerAuthenticator(
ApplicationSettings.Uri,
ApplicationSettings.AuthorizeUri,
ApplicationSettings.CallBackUri,
tokenStore,
certificate
);
var consumer = new Consumer(ApplicationSettings.Key,
ApplicationSettings.Secret);
var token = partnerAuthentication.GetToken(consumer,
XeroUser(UserId));
I have checked the keys are the partner keys in my app and any hints or sample code would be great.

How to verify JWT from AWS Cognito in the API backend?

I'm building a system consisting of an Angular2 single page app and a REST API running on ECS. The API runs on .Net/Nancy, but that might well change.
I would like to give Cognito a try and this is how I imagined the authentication workflow:
SPA signs in user and receives a JWT
SPA sends JWT to REST API with every request
REST API verfies that the JWT is authentic
My question is about step 3. How can my server (or rather: my stateless, auto-scaled, load-balanced Docker containers) verify that the token is authentic? Since the "server" hasn't issued the JWT itself, it can't use its own secret (as described in the basic JWT example here).
I have read through the Cognito docs and googled a lot, but I can't find any good guideline about what to do with the JWT on the server side.
Turns out I didn't read the docs right. It's explained here (scroll down to "Using ID Tokens and Access Tokens in your Web APIs").
The API service can download Cognito's secrets and use them to verify received JWT's. Perfect.
Edit
#Groady's comment is on point: but how do you validate the tokens? I'd say use a battle-tested library like jose4j or nimbus (both Java) for that and don't implement the verification from scratch yourself.
Here's an example implementation for Spring Boot using nimbus that got me started when I recently had to implement this in java/dropwizard service.
Here's a way to verify the signature on NodeJS:
var jwt = require('jsonwebtoken');
var jwkToPem = require('jwk-to-pem');
var pem = jwkToPem(jwk);
jwt.verify(token, pem, function(err, decoded) {
console.log(decoded)
});
// Note : You can get jwk from https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
Execute an Authorization Code Grant Flow
Assuming that you:
have correctly configured a user pool in AWS Cognito, and
are able to signup/login and get an access code via:
https://<your-domain>.auth.us-west-2.amazoncognito.com/login?response_type=code&client_id=<your-client-id>&redirect_uri=<your-redirect-uri>
Your browser should redirect to <your-redirect-uri>?code=4dd94e4f-3323-471e-af0f-dc52a8fe98a0
Now you need to pass that code to your back-end and have it request a token for you.
POST https://<your-domain>.auth.us-west-2.amazoncognito.com/oauth2/token
set your Authorization header to Basic and use username=<app client id> and password=<app client secret> per your app client configured in AWS Cognito
set the following in your request body:
grant_type=authorization_code
code=<your-code>
client_id=<your-client-id>
redirect_uri=<your-redirect-uri>
If successful, your back-end should receive a set of base64 encoded tokens.
{
id_token: '...',
access_token: '...',
refresh_token: '...',
expires_in: 3600,
token_type: 'Bearer'
}
Now, according to the documentation, your back-end should validate the JWT signature by:
Decoding the ID token
Comparing the local key ID (kid) to the public kid
Using the public key to verify the signature using your JWT library.
Since AWS Cognito generates two pairs of RSA cryptograpic keys for each user pool, you need to figure out which key was used to encrypt the token.
Here's a NodeJS snippet that demonstrates verifying a JWT.
import jsonwebtoken from 'jsonwebtoken'
import jwkToPem from 'jwk-to-pem'
const jsonWebKeys = [ // from https://cognito-idp.us-west-2.amazonaws.com/<UserPoolId>/.well-known/jwks.json
{
"alg": "RS256",
"e": "AQAB",
"kid": "ABCDEFGHIJKLMNOPabc/1A2B3CZ5x6y7MA56Cy+6ubf=",
"kty": "RSA",
"n": "...",
"use": "sig"
},
{
"alg": "RS256",
"e": "AQAB",
"kid": "XYZAAAAAAAAAAAAAAA/1A2B3CZ5x6y7MA56Cy+6abc=",
"kty": "RSA",
"n": "...",
"use": "sig"
}
]
function validateToken(token) {
const header = decodeTokenHeader(token); // {"kid":"XYZAAAAAAAAAAAAAAA/1A2B3CZ5x6y7MA56Cy+6abc=", "alg": "RS256"}
const jsonWebKey = getJsonWebKeyWithKID(header.kid);
verifyJsonWebTokenSignature(token, jsonWebKey, (err, decodedToken) => {
if (err) {
console.error(err);
} else {
console.log(decodedToken);
}
})
}
function decodeTokenHeader(token) {
const [headerEncoded] = token.split('.');
const buff = new Buffer(headerEncoded, 'base64');
const text = buff.toString('ascii');
return JSON.parse(text);
}
function getJsonWebKeyWithKID(kid) {
for (let jwk of jsonWebKeys) {
if (jwk.kid === kid) {
return jwk;
}
}
return null
}
function verifyJsonWebTokenSignature(token, jsonWebKey, clbk) {
const pem = jwkToPem(jsonWebKey);
jsonwebtoken.verify(token, pem, {algorithms: ['RS256']}, (err, decodedToken) => clbk(err, decodedToken))
}
validateToken('xxxxxxxxx.XXXXXXXX.xxxxxxxx')
AWS released a JavaScript library specifically for this purpose: https://github.com/awslabs/aws-jwt-verify.
The library has similar machinery to other libraries out there and mentioned here, such as automatically downloading, and caching, the JWKS (the public keys with which Cognito JWTs can be verified). It's written in pure TypeScript and has 0 dependencies.
import { CognitoJwtVerifier } from "aws-jwt-verify";
// Verifier that expects valid access tokens:
const verifier = CognitoJwtVerifier.create({
userPoolId: "<user_pool_id>",
tokenUse: "access",
clientId: "<client_id>",
});
try {
const payload = await verifier.verify(
"eyJraWQeyJhdF9oYXNoIjoidk..." // the JWT as string
);
console.log("Token is valid. Payload:", payload);
} catch {
console.log("Token not valid!");
}
(By the way, the library also includes a class that works for other identity providers than Cognito)
Disclaimer: I'm one of the authors of the library. We're looking forward to customer feedback––do leave us a GitHub issue.
Short answer:
You can get the public key for your user pool from the following endpoint:
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
If you successfully decode the token using this public key then the token is valid else it is forged.
Long answer:
After you successfully authenticate via cognito, you get your access and id tokens. Now you want to validate whether this token has been tampered with or not. Traditionally we would send these tokens back to the authentication service (which issued this token at the first place) to check if the token is valid. These systems use symmetric key encryption algorithms such as HMAC to encrypt the payload using a secret key and so only this system is capable to tell if this token is valid or not.
Traditional auth JWT token Header:
{
"alg": "HS256",
"typ": "JWT"
}
Note here that encryption algorithm used here is symmetric - HMAC + SHA256
But modern authentication systems like Cognito use asymmetric key encryption algorithms such as RSA to encrypt the payload using a pair of public and private key. Payload is encrypted using a private key but can be decoded via public key. Major advantage of using such an algorithm is that we don't have to request a single authentication service to tell if a token is valid or not. Since everyone has access to the public key, anyone can verify validity of token. The load for validation is fairly distributed and there is no single point of failure.
Cognito JWT token header:
{
"kid": "abcdefghijklmnopqrsexample=",
"alg": "RS256"
}
Asymmetric encryption algorithm used in this case - RSA + SHA256
cognito-jwt-verifier is a tiny npm package to verify ID and access JWT tokens obtained from AWS Cognito in your node/Lambda backend with minimal dependencies.
Disclaimer: I'm the author of this. I came up with it because I couldn't find anything checking all the boxes for me:
minimal dependencies
framework agnostic
JWKS (public keys) caching
test coverage
Usage (see github repo for a more detailed example):
const { verifierFactory } = require('#southlane/cognito-jwt-verifier')
const verifier = verifierFactory({
region: 'us-east-1',
userPoolId: 'us-east-1_PDsy6i0Bf',
appClientId: '5ra91i9p4trq42m2vnjs0pv06q',
tokenType: 'id', // either "access" or "id"
})
const token = 'eyJraWQiOiI0UFFoK0JaVE...' // clipped
try {
const tokenPayload = await verifier.verify(token)
} catch (e) {
// catch error and act accordingly, e.g. throw HTTP 401 error
}
I had a similar problem but without using the API Gateway. In my case I wanted to verify the signature of a JWT token obtained via the AWS Cognito Developer Authenticated identity route.
Like many posters on various sites I had trouble piecing together exactly the bits I needs to verify the signature of an AWS JWT token externally i.e., server side or via script
I think I figured out out and put a gist to verify an AWS JWT token signature. It'll verify an AWS JWT/JWS token with either pyjwt or PKCS1_v1_5c from Crypto.Signature in PyCrypto
So, yes this was python in my case but it's also doable easily in node (npm install jsonwebtoken jwk-to-pem request).
I attempted to highlight some gotchas in the comments because when I was trying to figure this out I was mostly doing the right thing but there were some nuances like python dict ordering, or lack there of, and json representation.
Hopefully it may help somebody somewhere.
You can get insights from the Lambda code here
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
https://github.com/awslabs/aws-support-tools/tree/master/Cognito/decode-verify-jwt
In Golang
https://gist.github.com/tmaiaroto/e2ee5e88fc6ae035307d7c5ee71a99cf
this is working for me in dot net 4.5
public static bool VerifyCognitoJwt(string accessToken)
{
string[] parts = accessToken.Split('.');
string header = parts[0];
string payload = parts[1];
string headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
JObject headerData = JObject.Parse(headerJson);
string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
JObject payloadData = JObject.Parse(payloadJson);
var kid = headerData["kid"];
var iss = payloadData["iss"];
var issUrl = iss + "/.well-known/jwks.json";
var keysJson= string.Empty;
using (WebClient wc = new WebClient())
{
keysJson = wc.DownloadString(issUrl);
}
var keyData = GetKeyData(keysJson,kid.ToString());
if (keyData==null)
throw new ApplicationException(string.Format("Invalid signature"));
var modulus = Base64UrlDecode(keyData.Modulus);
var exponent = Base64UrlDecode(keyData.Exponent);
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
var rsaParameters= new RSAParameters();
rsaParameters.Modulus = new BigInteger(modulus).ToByteArrayUnsigned();
rsaParameters.Exponent = new BigInteger(exponent).ToByteArrayUnsigned();
provider.ImportParameters(rsaParameters);
SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider();
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(parts[0] + "." + parts[1]));
RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(provider);
rsaDeformatter.SetHashAlgorithm(sha256.GetType().FullName);
if (!rsaDeformatter.VerifySignature(hash, Base64UrlDecode(parts[2])))
throw new ApplicationException(string.Format("Invalid signature"));
return true;
}
public class KeyData
{
public string Modulus { get; set; }
public string Exponent { get; set; }
}
private static KeyData GetKeyData(string keys,string kid)
{
var keyData = new KeyData();
dynamic obj = JObject.Parse(keys);
var results = obj.keys;
bool found = false;
foreach (var key in results)
{
if (found)
break;
if (key.kid == kid)
{
keyData.Modulus = key.n;
keyData.Exponent = key.e;
found = true;
}
}
return keyData;
}
Someone also wrote a python package called cognitojwt that works in both async/sync mode to decode and verify Amazon Cognito JWT.
This is based on the elaborate explanation from Derek (answer). I have been able to create a working sample for PHP.
I have used https://github.com/firebase/php-jwt for pem creation and code verification.
This code is used after you received a set of base64 encoded tokens.
<?php
require_once(__DIR__ . '/vendor/autoload.php');
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;
function debugmsg($msg, $output) {
print_r($msg . "\n");
}
$tokensReceived = array(
'id_token' => '...',
'access_token' => '...',
'refresh_token' => '...',
'expires_in' => 3600,
'token_type' => 'Bearer'
);
$idToken = $tokensReceived['id_token'];
// 'https://cognito-idp.us-west-2.amazonaws.com/<pool-id>/.well-known/jwks.json'
$keys = json_decode('<json string received from jwks.json>');
$idTokenHeader = json_decode(base64_decode(explode('.', $idToken)[0]), true);
print_r($idTokenHeader);
$remoteKey = null;
$keySets = JWK::parseKeySet($keys);
$remoteKey = $keySets[$idTokenHeader['kid']];
try {
print_r("result: ");
$decoded = JWT::decode($idToken, $remoteKey, array($idTokenHeader['alg']));
print_r($decoded);
} catch(Firebase\JWT\ExpiredException $e) {
debugmsg("ExpiredException","cognito");
} catch(Firebase\JWT\SignatureInvalidException $e) {
debugmsg("SignatureInvalidException","cognito");
} catch(Firebase\JWT\BeforeValidException $e) {
debugmsg("BeforeValidException","cognito");
}
?>

Asana Authorization error on Mono.NET framework

I'm trying to use the Asana restful API and I receive this error:
{"errors":[{"message":"Not Authorized"}]}
public static string GetProjects()
{
string url = "https://app.asana.com/api/1.0/projects/"; // Constants.BaseApiUrl + "projects";
var client = new RestClient(url);
System.Net.ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult);
client.Authenticator = new HttpBasicAuthenticator(AsanaAPIKey.GetBase64(), "");
var req = new RestRequest(Method.GET);
RestResponse res =(RestResponse) client.Execute(req);
return res.Content;
}
public static bool CheckValidationResult(object sp,
X509Certificate cert,
X509Chain req,
System.Net.Security.SslPolicyErrors problem)
{
return true;
}
I've tried plain httpwebrequest/Httpwebresponse and it didn't work either so I tried the restsharp library and still the same problem.
Any ideas why this error is happening?
I don't know .NET but I see you're creating an HttpBasicAuthenticator and it looks like you're passing it a username/password pair. But you are passing it a base64-encoded version of the API key, which is wrong. The documentation on authentication states that when using an HTTP library you should pass the API key as the username, unchanged. You only need to manually base64-encode if you are constructing the full header manually.

Disabling encryption in Windows Identity Foundation

Can I disable encryption of the request security token response and only manage signatures?
I'm creating a custom STS extending Microsoft.IdentityModel.SecurityTokenService.SecurityTokenService based on the demos of the WIF SDK and I cannot manage to setup not using encryption.
I just ran the "Add STS Reference" wizard in Visual Studio, selecting the option to create a new STS. The template that the tool generated does add support for token encryption, but if no cert is supplied, thne it is disabled: (I left all the default comments)
protected override Scope GetScope( IClaimsPrincipal principal, RequestSecurityToken request )
{
ValidateAppliesTo( request.AppliesTo );
//
// Note: The signing certificate used by default has a Distinguished name of "CN=STSTestCert",
// and is located in the Personal certificate store of the Local Computer. Before going into production,
// ensure that you change this certificate to a valid CA-issued certificate as appropriate.
//
Scope scope = new Scope( request.AppliesTo.Uri.OriginalString, SecurityTokenServiceConfiguration.SigningCredentials );
string encryptingCertificateName = WebConfigurationManager.AppSettings[ "EncryptingCertificateName" ];
if ( !string.IsNullOrEmpty( encryptingCertificateName ) )
{
// Important note on setting the encrypting credentials.
// In a production deployment, you would need to select a certificate that is specific to the RP that is requesting the token.
// You can examine the 'request' to obtain information to determine the certificate to use.
scope.EncryptingCredentials = new X509EncryptingCredentials( CertificateUtil.GetCertificate( StoreName.My, StoreLocation.LocalMachine, encryptingCertificateName ) );
}
else
{
// If there is no encryption certificate specified, the STS will not perform encryption.
// This will succeed for tokens that are created without keys (BearerTokens) or asymmetric keys.
scope.TokenEncryptionRequired = false;
}
// Set the ReplyTo address for the WS-Federation passive protocol (wreply). This is the address to which responses will be directed.
// In this template, we have chosen to set this to the AppliesToAddress.
scope.ReplyToAddress = scope.AppliesToAddress;
return scope;
}
I create a CustomSecurityHandler and override its GetEncryptingCredentials method returning null value like the following lines and it works:
public class MyCustomSecurityTokenHandler : Saml11SecurityTokenHandler
{
public MyCustomSecurityTokenHandler(): base() {}
protected override EncryptingCredentials GetEncryptingCredentials(SecurityTokenDescriptor tokenDescriptor)
{
return null;
}
}
then in the SecurityTokenService class i override the GetSecurityTokenHandler returning the custom class created before:
protected override SecurityTokenHandler GetSecurityTokenHandler(string requestedTokenType)
{
MyCustomSecurityTokenHandler tokenHandler = new MyCustomSecurityTokenHandler();
return tokenHandler;
}