wtorek, 29 września 2015

[Security] JsonWebTokens

Celem jest stworzenie struktury reprezentującej informacje na temat tożsamości, wydającego itd. Security tokens są chronionymi strukturami danych (podpisane, zawierają czas wygasanie). Składają się z trzech sekcji: nagłówka, payloadu oraz podpisu weryfikującego.

Proces wydawania tokenu może zastępować klasyczny proces autentykacji użytkownika do aplikacji (zamiast cookie otrzymuje on token będący stringiem, który później dołącza do swoich żądan, np. jako nagłówek HTTP).

JWT wykorzystuje różne mechanizmy kryptograficzne. Najpopularniejszym z nich jest HMACSHA256, który "wylicza" skrót z contentu przy użyciu klucza symetrycznego. W innych wariantach można spotkać się z RSA, a nawet z kryptografią krzywych eliptycznych. W przypadku aplikacji webowych serwer wydaje tokeny i ten sam serwer je później weryfikuje, więc tajny klucz znajduje się w jednym miejscu. Aplikacja JS może takie tokeny dołączać np. jako nagłówek HTTP.

Przykładowy JWT wraz z zdekodowaną postacią (kodowanie Base64).




Serwerowo w .NET mamy do dyspozycji NuGet od Microsoftu.



Przykładowa aplikacja w C# weryfikująca integralność na podstawie współdzielonego klucza:

static void Main(string[] args)
{
    var pass = "AC917771A299CF9542513AED8885D12";
    var token = CreateJsonWebToken(pass);

    Console.WriteLine(token);

    var payload = ParseAndValidateJwtToken(token, pass);
    Console.WriteLine(payload.Last().Value);

    token = token.Replace("Z", "6");

    ParseAndValidateJwtToken(token, pass);

    Console.Read();
}

private static string CreateJsonWebToken(string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();

    var descriptor = new SecurityTokenDescriptor();
    DateTime now = DateTime.Now;

    var key = System.Text.Encoding.Default.GetBytes(pass);
    var securityKey = new InMemorySymmetricSecurityKey(key);

    descriptor.TokenIssuerName = "mySecretApp";
    descriptor.AppliesToAddress = "http://mydomain.com";
    descriptor.Lifetime = new System.IdentityModel.Protocols.WSTrust.Lifetime(now, now.AddHours(1));
    descriptor.Subject = new System.Security.Claims.ClaimsIdentity();
    descriptor.SigningCredentials = new SigningCredentials(securityKey,
        "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
        "http://www.w3.org/2001/04/xmlenc#sha256");


    JwtSecurityToken token = (JwtSecurityToken)tokenHandler.CreateToken(descriptor);
    token.Payload.Add("myKey", "myValue");            

    return tokenHandler.WriteToken(token);
}

private static JwtPayload ParseAndValidateJwtToken(string jwtToken, string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    try
    {
        ValidationProcedure(jwtToken, pass);
    }            
    catch(SignatureVerificationFailedException e)
    {
        Console.WriteLine("Tampering attack detected");
    }

    JwtSecurityToken parsedJwt = tokenHandler.ReadToken(jwtToken) as JwtSecurityToken;
    return parsedJwt.Payload;
}

private static void ValidationProcedure(string token, string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = System.Text.Encoding.Default.GetBytes(pass);
    var securityKey = new InMemorySymmetricSecurityKey(key);

    TokenValidationParameters validationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        RequireSignedTokens = true,
        ValidateAudience = false,
        IssuerSigningKey = securityKey
    };

    SecurityToken securityToken;
    ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
}
}