Monday, 16 October 2017

JWT, JWE, JWS in .Net - Pt 3 - JWE

JWE is the encrypted version of a JWT. Encryption provides a way to ensure privacy of the data from an attacker and if using a pre-shared key, a very strong way of transmitting private data.

The .Net version of the JWT libraries does not also require a signature to be applied, you could assume that the data has integrity if you use an AEAD algorithm for encryption - which you should. However, it appears that you cannot validate the token if it does not have a signature - I'm not sure if there is a way to do that or whether it does not make sense to validate a token with no signature?

Fortunately, to produce a JWE in .Net is very similar to producing a JWS, although you need to generate a cryptographically secure symmetrical key as well as using a certificate to sign it. Naturally, all of this has overhead so although encryption-by-default can be useful, it does come at a price, especially for high-volume systems.

To create a key (the Content Encryption Key - CEK) , you can either just use RNGCryptoServiceProvider from the System.Security.Cryptography namespace like this:

var crng = new RNGCryptoServiceProvider();
var keyBytes = new byte[32];   // 256 bits for AES256
crng.GetBytes(keyBytes);

Or you can hash some other piece of data using SHA256 to stretch it. Be careful with this method since you need the input to the SHA function to already be cryptographically secure random or an attacker could discover a pattern and work out how you generate your keys! For instance, do not stretch a numeric userid or guid. In my case, I was stretching a 32 character randomly generated "secret" from an oauth credential to create my pre-shared key.

var keyBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("some data to stretch"));

Be careful with SHA256 and other cryptography classes for thread safety. It might be quicker to pre-create certain types like SHA256 but if ComputeHash is not thread safe, you might break something when used by multiple threads. I believe some forms of the .Net cryptography classes are thread safe and others are not.

Once you have your CEK, the only extra step is to create EncryptingCredentials as well as SigningCredentials:

var symSecurityKey = new SymmetricSecurityKey(keyBytes);
var creds = new EncryptingCredentials(symSecurityKey, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512);

Note that you need to use algorithms that are supported in .Net (I can't guarantee that the SecurityAlgorithms enum equates to what is supported), that the selected algorithms match the length of the key provided (i.e. 32 bytes for AES256) and that the second algorithm, which is used to encrypt the actual data is a type that includes authenticated data - i.e. a signature for the encrypted data to verify it was not tampered with before decrypting (such as GCM or HMACSHA). If you choose the wrong length of key, the call to CreateSecurityToken will throw a ArgumentOutOfRangeException. The first algorithm is the one that will be used to encrypt the key itself before it is added to the JWE token.

You can use RSA-OAEP for the first parameter but this is not the same as when it is used for the JWE. Firstly, it will only use a 256 bit key for RSA to match the second algorithm (the size of the key) but also, it will need a public key to encrypt and the recipient of the token will need the related private key to decrypt the CEK.

By providing the SigningCredentials and EncryptingCredentials to the call to CreateSecurityToken(), the library will create the token, sign it and then encrypt this as the payload in an outer JWE. This means that the header for the JWT will only contain data about the encrypting parameters (alg, enc etc) and only after it is decrypted, will the signing parameters be visible.

As mentioned before, you do not have to set a SigningCredential but when I tried this, the call to ValidateToken failed which sounds like it cannot validate data that is only encrypted, which might be possible to bypass (since the encrypted data already requires the use of an authenticated algorithm),

Validating is otherwise the same as it is for JWS, except for also setting the value of the TokenDecryptionKey in the TokenValidationParameters in the same way as it was set when it was created.

JWT, JWE, JWS in .Net - Pt 2 - Validating JWS

Fortunately, validating a JWS (and for that matter, a JWE) is very straight-forward thanks to JwtSecurityTokenHandler.ValidateToken().

Quite simply, you take the serialized string, create a TokenValidationParameters object with the relevant fields filled in to validate and then call ValidateToken, it looks like the following. Note that the same code is used for JWS and JWE tokens, the only difference is whether you fill in the TokenDecryptionKey property. This shows both:

 private ClaimsPrincipal ValidateToken(string tokenString, byte[] keybytes)  
 {  
   var signingkey = new X509SecurityKey(new X509Certificate2(certFilePath,certPassword));  
   var jwt = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(tokenString);  
   // Verification  
   var tokenValidationParameters = new TokenValidationParameters()  
   {  
     ValidAudiences = new string[]  
     {  
       "123456"  // Needs to match what was set in aud property of token
     },  
     ValidIssuers = new string[]  
     {  
       "https://logindev.pixelpin.co.uk"  // Needs to match iss property of token
     },  
     IssuerSigningKey = signingkey,  
     TokenDecryptionKey = keybytes == null ? null : new SymmetricSecurityKey(keybytes)  
   };  
   SecurityToken validatedToken;  
   var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();  
   return handler.ValidateToken(tokenString, tokenValidationParameters, out validatedToken);  
 }  

In my method (in a Unit Test), I simply return the ClaimsPrincipal that is returned from ValidateToken() but you could also get the validated and decrypted token that is returned as an out parameter if you wanted to continue to use it.

Also note that I am simply loading the same pfx I used to sign the token to validate it, whereas in real like, you are likely to visit the url of the issuer and perhaps https://theurl.com/.well-known/jwks and find the public key for the signed data using the kid property from the token.

This method allows the caller to pass null for the keybytes if only validating a signed JWS or real key bytes matching the encryption key used for the JWE. This is for pre-shared keys only. In a later post, I will talk about extracting the encryption key, which is actually embedded in a JWE and does not need to be pre-shared.

In part 3, we'll look at JWE (encrypted JWT)

Friday, 13 October 2017

JWT, JWE, JWS in .Net

JWT in .Net

When I first approached the idea of doing JWT (json web tokens) in .Net, it all seemed a little confusing.

Firstly, it IS confusing because Microsoft have started with a Microsoft.IdentityModel.Tokens namespace, which was eventually migrated into System.IdentityModel.Tokens, deprecating the original namespace but THEN, they added new functionality in System.IdentityModel.Tokens version 5 that references NEW code in Microsoft.IdentityModel.Tokens (which is resurrected). All the usual chaos has started since some things are the same (most class names), some are different. Some code written for v4 of System.IdentityModel.Tokens will not work in version 5. Anyway...

The Basics

Before you can understand how to do this, you should know what json is (JavaScript Object Notation), which is a fairly small way to move data around - much smaller than xml for instance, but it is generally web friendly.

You should also understand the basic concepts of signing and encryption.

Signing using asymmetric key encryption (RSA, DSA etc) allows you to create a packet of data, sign it with your private key and send it to a recipient. Even though the data is NOT private because it is NOT encrypted, the recipient can use your PUBLIC key to verify the signature that you applied to the data, which provides 2 protections (assuming keys are secure etc.) Firstly, it provides integrity of the data. An attacker could not modify the data and leave a valid signature since the private key needed to produce the signature is not available to the attacker. The recipient would know this when they validate the token and should/must discard the data if the signature fails. Secondly. signing provides non-repudiation, which means the sender cannot deny signing the data unless they admit to losing their private keys to an attacker.

It is also possible to sign the data with a symmetrical key, which would be useful if the sender and receiver already have a securely shared secret, which would therefore remove the need to perform asymmetrical signing/verifying, which is computationally expensive. (See Amey's answer here). Obviously this means that either party or anyone who is given this secret can also sign data so is not normally used.

Encryption is about obscuring the real data so an attacker cannot read the data when it is at rest or in transit. With encryption, it is assumed that either there is a securely shared key or that the same key can be derived using something like Diffie-Hellman key exchange.

JWT is an abstract idea that is made concrete in 2 sub-types.

JWS is a form of JWT for signed data, it is not encrypted.

JWE is an encrypted and signed form of JWT.

JWS - Json Web Token Signed

JWS is relatively straight-forward. It is composed of a json header with typ, alg and kid to identify the type ("jwt"), algorithm (signing algorithm, for example "RS256" or a URL for RSA) and the key identifier so the recipient knows which key can be used to verify the signature.

You will need the namespaces:

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography.X509Certificates;

You can create a header either explicitly in .Net or you can allow the helper method CreateSecurityToken to do it for you:

Method 1: Create the JwtHeader yourself (from certificate in this case)

var key = new X509SecurityKey(new X509Certificate2(certFilePath,certPassword));
var algorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";     // RS256
var creds = new SigningCredentials(key, algorithm);
var header = new JwtHeader(creds);

Method 2: Use CreateJwtSecurityToken helper method

var handler = new JwtSecurityTokenHandler();
var token = handler.CreateJwtSecurityToken(issuer, audience, null, creationTime, creationTime.AddSeconds(lifetime), creationTime, creds);
// token now has header automatically

In method 2, the payload is being populated at the same time as the header.

After the header is the payload, which is another json dictionary (a claims list) with some standard claims such as nbf (not before), exp (expiry), iss (issuer) and aud (audience i.e. recipient) as well as any other additional claims that you wish to send. Issuer can be any string that is relevant but if you are using public key discovery, it is useful to use a URL that can be used to lookup .well-known/jwks to see a list of keys related to key ids (kid in the header of the JWT)

If using the first method, you can create the payload in a number of ways but this is probably the easiest:

var payload = new JwtPayload(
    token.Issuer,
    token.Audience,
    null,
    token.CreationTime.UtcDateTime,
    token.CreationTime.AddSeconds(token.Lifetime).UtcDateTime);

payload.AddClaims(token.Claims);
var handler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken(header, payload);

token is simply an object that contains the data to serialize into JWT.

The second method above already shows how to add the required payload (nbf,exp,iss,aud) in the same call to CreateJwtSecurityToken. Other claims would need to be added afterwards simply by calling token.Payload.AddClaims().

Once these are created, the data is combined and the signature computed across the data using the specified algorithm and key. Once this is done, each of these is base64 encoded and concatenated with a period (.) into 3 blocks. This part is really easy because if you have specified your key correctly, you simply tell the handler to write the token:

var serializedJwt = handler.WriteToken(token);

The result might look something like this:

eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Iiwia2lkIjoiMjVGN0Y2NThDQ0E2NjI2QjRENTdBNEExQTMyOUUyMjBDMjNEQUI3QSIsInR5cCI6IkpXVCJ9
.eyJuYmYiOjE1MDc5MTA0NDAsImV4cCI6MTUwNzkxMDQ3MCwiaXNzIjoicHAiLCJhdWQiOiIxMjM0NTYiLCJteWRhdGEiOiJzb21ldGhpbmdpbmhlcmUifQ
.TNSLgLuj-j9eSC5HiQqS86LLjZ6ZJnGoMAkGMsTqY-pjptQ8qItmrrSE3bf12E5aYHfNr4IWmSdY4-qkQRUXmtb-Ev2c0wE5NkABYqRdAok-AHuBUcRds7VrEQTanB69sKAhtEIZHshLPc4D9IMlYpc2opOzTCBGIB15mX0HodF6hdP6-LeaEeM-rR8v6bnmMsuvzu3GSGOSvPRm_yvZv25ywp4IzEYlbmLcw6NBJt4fx_8ZSEvIQtvCYtMgtAkFbiJ85Lo3sY7m8o8w84ChIG4AgDQi-woRwGU-3RFouppmAgjqPMCgMYn5Tt7Q3rjVtAgulMv0z0tVNvmOVg0zt04sI-CJIkgimAdaNM-O35Lyh4DzPasOXM_HsZ5_3EQoQn1pVRNU_6iBy4X1vGTRNICZon0x3v2MLvQQskaKfbUkSEqs1mceKXgu3cp6GRdT18z1ZUduP5hNYrysCvXtYjmcmmjC-RbnjgNcv3vC51gZvoyQ5OvmtBIvYv_QF14paV1uIxxd8Z_P0z0Z4RrNokUkWwb2n4RYOmW0Ihs7HR-1ba6Cervh7noGM9MQnbSD9lLHu9aQRp9Jl7vS4YPJI98IizYOzCndRcDKpv9LsJNJgXX3OVpxaUqbEiRVGcYMu7m72mdj4kNAyO86JejnaEgkFItUSg8jAU6DnkBwDHU

NOTE: The spec uses URL friendly base 64, which means + becomes -, / becomes _ and the = symbol is stripped from the end.

In part 2, we'll describe how to validate the token on the other end.

SecurityTokenEncryptionFailedException: IDX10615: Encryption failed. No support for: Algorithm

Microsoft.IdentityModel.Tokens.SecurityTokenEncryptionFailedException: IDX10615: Encryption failed. No support for: Algorithm when trying to create an instance of Microsoft.IdentityModel.Tokens.EncryptingCredentials()

I tried variations for parameter 2 (alg) but none seemed to work. I was really foxed until I found the source code online and realised that I was passing in the signing key (RSA256) rather than the encryption key (AES256) for the first parameter!

The code will attempt to use the key's crypto factory to lookup the specified algorithm and obviously that won't work when trying to specify AES on an RSA key.

Just a typo!

While I'm here, the algorithms for alg and enc need to be the same length because they will use the same key (parameter 1).

Tuesday, 18 July 2017

.Net Web API Validation

So I'm writing a Web Api .Net service to call from some mobile apps. Before you ask, I haven't used .Net Core since it requires all the support libraries are portable and that is not a 5 minute job!

Anyway, it basically works but I found a couple of funnies that have been reported elsewhere but they are not things that are obviously broken - thank goodness for Unit Tests!

1) I have an attribute that validates the model required by the API action and then sets BadRequest if the model doesn't validate - this saves calling if (ModelState.IsValid) everywhere. It didn't seem to work, IsValid was true when I called an action with no parameters. The reason? If the model is null, it passes validation! Terrible but true. I had to add an additional line of code to ensure the model was null before checking whether it was valid.

2) The RegularExpressionAttribute does not validate empty strings according to the regex. It would be nice if it was a property of the attribute but it isn't, it just doesn't. Again, I had to subclass RegularExpressionAttribute, override IsValid to ensure the value is not empty and then call the base class IsValid. I then subclassed this into my specific Attributes so that they all work as expected.

Tuesday, 20 June 2017

Client Certificate does not appear in Windows Credential Manager

This is one of those jobs I have done several times but couldn't remember why it didn't work the next time.

You add a client certificate to your personal store under Current User, it is in-date, it chains OK but when using Windows Credential Manager to add a connection, it doesn't offer you this certificate to choose,.

As pointed out here, you have to edit the properties of the certificate and untick "Smart Card Logon" and "Any Purpose" otherwise Windows will ask for a Smart Card to access the client cert!

Wednesday, 14 June 2017

OutOfRangeInput One of the request inputs is out of range on Azure CDN

Setting up a new environment that was (theoretically) the same as an existing system. Created a new CDN on Azure, pointed it at blob storage and tried to access it and Azure gives you a rather esoteric (and apparently catch-all) error.

Most answers that I found related to using invalid naming i.e. requesting a table with upper-case letters, when tables are not allowed to have upper-case letters (which matches the error message).

The issue here is that the CDN is hiding an error that is actually a storage error and, surprise, surprise, is nothing to do with the request but is related to a permission error.

I had setup the storage blob with "Private" permission but it actually needs "Blob" permission, which allows anonymous to read but not write blobs.

I updated it to use the correct permission but it still didn't work because.....it's a CDN and everything takes ages to propagate. I waited a while and it worked.