Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Part 2 - Beyond OAuth 2.1

Part 2 - Beyond OAuth 2.1

NDC 2020

75681814fbbb90c9224ea5ed0f8324ee?s=128

Dominick Baier

June 10, 2020
Tweet

Transcript

  1. OAuth 2.1 & Beyond Dominick Baier @leastprivilege

  2. 2 @leastprivilege Me • Independent Consultant – Specializing on Application

    Security Architectures & Security Protocols – Working with Software Development Teams (ISVs and in-house) • Co-Creator of IdentityServer & IdentityModel OSS Project – Certified OpenID Connect & OAuth 2.0 Engine for ASP.NET Core – https://identityserver.io • Co-Creator of PolicyServer – Authorization for modern Applications – https://policyserver.io email dominick.baier@leastprivilege.com blog https://leastprivilege.com twitter @leastprivilege slides https://speakerdeck.com/leastprivilege
  3. 3 @leastprivilege High Security OAuth FAPI https://fapi.openid.net

  4. 4 @leastprivilege Agenda • OAuth security best current practices &

    OAuth 2.1 – common attacks, countermeasures & recommendations • Advanced OAuth – tokens, scopes, resources and audience restrictions – rich authorization requests (RAR) – JWT secured authorization requests & request objects (JAR) – pushed authorization requests (PAR) – strong client authentication – proof-of-possession access tokens – delegation, impersonation and token exchange – "OAuth 3.0" outlook https://identityserver.io/training/advanced-oauth.html
  5. 5 @leastprivilege Agenda • OAuth 2.0 to 2.1 diff •

    Beyond – improving front-channel security – improving back-channel security – proof-of-possession access tokens
  6. 6 @leastprivilege Relevant Documents OAuth 2.0 Security Best Current Practice

    https://tools.ietf.org/html/draft-ietf-oauth- security-topics/ OAuth 2.0 Threat Model & Security Considerations https://tools.ietf.org/html/rfc6819 JSON Web Token Best Current Practices https://tools.ietf.org/html/rfc8725 OAuth 2.0 for native Applications https://tools.ietf.org/html/rfc8252 OAuth 2.0 for Browser-Based Applications https://tools.ietf.org/wg/oauth/draft-ietf-oauth-browser-based-apps/ JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens https://tools.ietf.org/wg/oauth/draft-ietf-oauth- access-token-jwt/
  7. 7 @leastprivilege OAuth 2.1 • RFC 6749 + RFC 6750

    + various BCPs == OAuth 2.1 – https://tools.ietf.org/html/draft-parecki-oauth-v2-1 • Most important changes – omission of implicit grant – omission of password grant – PKCE must be used with the authorization code grant – redirect URIs must be compared using exact string matching – refresh tokens must be either sender-constrained or one-time use only – bearer tokens usage omits token in the query string
  8. 8 @leastprivilege Improved Front-Channel with RAR, JAR & PAR

  9. 9 @leastprivilege Rich Authorization Requests (RAR) • scope parameter is

    typically coarse grained – some implementations use custom structured format, e.g. transaction:id • RAR introduces new authorization_detail parameter – "allow client to make a payment of 45€" – "allow client to read folder X, and write file Y" • Outcome of decision can be used to restrict access token to certain action(s) – typically combined with consent https://tools.ietf.org/html/draft-ietf-oauth-rar
  10. 10 @leastprivilege Example [ { "type": "customer_information", "locations": [ "https://example.com/customers"

    ], "actions": [ "read" ], "datatypes": [ "contacts" ] }, { "type": "customer_images", "locations": [ "https://example.com/images" ], "actions": [ "print" ], "datatypes": [ "photos" ], "identifier": "1" } ] represents the kinds of data being requested from the resource A string identifier indicating a specific resource available at the API schema
  11. 11 @leastprivilege Example (2) { "type": "https://scheme.example.org/files", "locations": [ "https://example.com/files"

    ], "permissions": [ { "path": "/myfiles/A", "access": [ "read" ] }, { "path": "/myfiles/A/X", "access": [ "read", "write" ] } ] }
  12. 12 @leastprivilege Example Authorization Request GET /authorize?response_type=code &client_id=s6BhdRkqt3 &state=af0ifjsldkj &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

    &code_challenge_method=S256 &code_challenge=K2-ltc83acc4h0c9w6ESC_rEMTJ3bwc-uCHaoeK1t8U &authorization_details=%5B%7B%22type%22%3A%22account%5Finformati on%22%2C%22actions%22%3A%5B%22list%5Faccounts%22%2C%22read%5Fbal ances%22%2C%22read%5Ftransactions%22%5D%2C%22locations%22%3A%5B% 22https%3A%2F%2Fexample%2Ecom%2Faccounts%22%5D%7D%5D HTTP/1.1 Host: server.example.com
  13. 13 @leastprivilege Access Token • Included either in JWT or

    introspection response { "iss": "http://as.com", "aud": "https://example.com/images" "iat": 1588420700 "exp": 1588420800, "sub": "123", "client_id": "456" "authorization_details": [ … ] }
  14. 14 @leastprivilege JWT Secured Authorization Requests (JAR) • Allows passing

    authorization parameters as a JSON Web Token – signed for integrity and client authentication – encrypted for confidentiality • Mitigates several known attacks – redirection URI re-writing – mix-up attacks https://tools.ietf.org/html/draft-ietf-oauth-jwsreq
  15. 15 @leastprivilege JWT Secured Authorization Request GET /authorize?client_id=client&response_type=code&redirect_uri=https://myapp.com/cb &state=abc&code_challenge=def&scope=openid customer.api

    GET /authorize?client_id=client&request= eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ewogICAgImlzcyI6ICJzNkJoZF JrcXQzIiwKICAgIC.JhdWQiOiAiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iL Aog ICAgInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsCiAgICAiY2xpZW 50X2 lkIjogInM2QmhkUmtxdDMiLAogICAgInJlZGlyZWN0X3VyaSI6ICJodHR wczovL2Ns aWVudC5leGFtcGxlLm9yZy9jYiIsCiAgICAic2NvcGUiOiAib3Blbml kIiwKICAgIC JzdGF0ZSI6ICJhZjBpZmpzbGRraiI.sCiAgICAibm9uY2UiOiAibi0wU zZfV3pBMk1q IiwKICAgICJtYXhfYWdlIjogODY0MDAKfQ.Nsxa_18VUElVaPjqW _ToI1yrEJ67BgK b5xsuZRVqzGkfKrOIX7BCx0biSxYGmjK9KJPctH1OC0iQJwXu5Y
  16. 16 @leastprivilege Request Object { "typ": "oauth.authz.req+jwt", "alg": "RS256", "kid":

    "1" }. { "iss": "client", "aud": "https://authorizationserver.com", "response_type": "code", "client_id": "client", "redirect_uri": "https://myapp.com/cb", "scope": "openid customer.api", "state": "abc", "code_challenge": "def" }. [Signature]
  17. 17 @leastprivilege Enabling JAR in IdentityServer new Client { ClientId

    = "jar.client", AllowedGrantTypes = GrantTypes.Code, RequireRequestObject = true, ClientSecrets = { new Secret { Type = IdentityServerConstants.SecretTypes.JsonWebKey, Value = "{'e':'AQAB','kid':'Zz..EA','kty':'RSA','n':'wW..Kw'}" } } }
  18. 18 @leastprivilege Pushed Authorization Requests (PAR) • Complements JAR •

    Provides interoperable endpoint to push authorization request parameters – in exchange for a request_uri – allows for authentication for confidential clients – manages entropy and lifetime https://tools.ietf.org/html/draft-ietf-oauth-par
  19. 19 @leastprivilege Pushed Authorization Request & Response POST /par client_id=client&

    response_type=code& redirect_uri=https://myapp.com/cb& state=abc& code_challenge=def& authorization_details={…} HTTP/1.1 201 Created { "request_uri": "urn:par:bwc4JK-cc191e-Y1LTC2", "expires_in": 90 }
  20. 20 @leastprivilege Authorization Request using request_uri • Front-channel only used

    to transmit reference to authenticated and validated request object GET /authorize?client_id=client& request_uri=urn:par:bwc4JK-cc191e-Y1LTC2
  21. 21 @leastprivilege Summary • RAR allows for structured resource access

    – replaces scope • JAR allows for signed, authenticated authorization requests – also encrypted if needed – also allows transmitting additional (trusted) data • PAR provides endpoint to push authorization request – removes all data from front-channel
  22. 22 @leastprivilege Improved Back-Channel with stronger Authentication and PoP

  23. 23 @leastprivilege Shared Secrets POST /token grant_type=client_credentials client_id=client client_secret=secret

  24. 24 @leastprivilege Recommendations • Use client secrets based on asymmetric

    keys – AS does not need to store any secret (only the public key) – secret not exposed over the wire • private_key_jwt – https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication – https://tools.ietf.org/html/rfc7523 • Mutual TLS – https://tools.ietf.org/html/rfc8705
  25. 25 @leastprivilege private_key_jwt • Client creates a JWT and signs

    it with its private key – can add additional custom claims Claim Description iss must contain the client ID of the OAuth client sub must contain the client ID of the OAuth client aud identifies the AS, e.g. URL of token endpoint or issuer name jti unique identifier for the token, which can be used to prevent reuse of the token exp expiration time of JWT iat time at which the jwt was issued (optional)
  26. 26 @leastprivilege Sending a private_key_jwt • Assertion type – urn:ietf:params:oauth:client-assertion-type:jwt-bearer

    POST /token grant_type=client_credentials client_assertion_type=urn:… client_assertion=<jwt>
  27. 27 @leastprivilege Creating a Client JWT private static string CreateClientToken(SigningCredentials

    credential, string clientId, string audience) { var now = DateTime.UtcNow; var token = new JwtSecurityToken( clientId, audience, new List<Claim>() { new Claim(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()), new Claim(JwtClaimTypes.Subject, clientId), new Claim(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64) }, now, now.AddMinutes(1), credential ); var tokenHandler = new JwtSecurityTokenHandler(); return tokenHandler.WriteToken(token); }
  28. 28 @leastprivilege Sending a private_key_jwt static async Task<TokenResponse> RequestTokenAsync(SigningCredentials credential)

    { var client = new HttpClient(); var clientToken = CreateClientToken(credential, "client.jwt", disco.TokenEndpoint); var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, Scope = "api1", ClientAssertion = { Type = OidcConstants.ClientAssertionTypes.JwtBearer, Value = clientToken } }); if (response.IsError) throw new Exception(response.Error); return response; }
  29. 29 @leastprivilege private_key_jwt and ASP.NET Core Clients public class OidcEvents

    : OpenIdConnectEvents { private readonly ClientTokenService _clientTokenService; public OidcEvents(ClientTokenService clientTokenService) { _clientTokenService = clientTokenService; } public override Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context) { context.TokenEndpointRequest.ClientAssertionType = OidcConstants.ClientAssertionTypes.JwtBearer; context.TokenEndpointRequest.ClientAssertion = _clientTokenService.CreateClientToken(); return Task.CompletedTask; } }
  30. 30 @leastprivilege Enabling private_key_jwt in IdentityServer • Wires up parser

    and validator • Adds replay cache based on IDistributedCache – can be replaced var builder = services.AddIdentityServer() .AddJwtBearerClientAuthentication();
  31. 31 @leastprivilege Client Definition in IdentityServer new Client { ClientId

    = "private.key.jwt", AllowedGrantTypes = GrantTypes.CodeAndClientCredentials, RequirePkce = true, ClientSecrets = { new Secret { Type = IdentityServerConstants.SecretTypes.X509CertificateBase64, Value = "M..A=" }, new Secret { Type = IdentityServerConstants.SecretTypes.JsonWebKey, Value = "{'e':'AQAB','kid':'Zz..EA','kty':'RSA','n':'wW..Kw'}" } } }
  32. 32 @leastprivilege "Proof-of-Possession" History • Most wanted feature since initial

    OAuth 2.0 spec – only bearer tokens were specified • Several attempts to introduce PoP at the application layer – required a lot of application layer crypto (e.g. signing of HTTP requests) – some unsolved problems (e.g. streaming) • ...then at the transport layer – HTTP token binding (deprecated)
  33. 33 @leastprivilege Weakness of Bearer Tokens • Bearer tokens are

    not bound to the client – attacker can replay leaked tokens Authorization: Bearer <token>
  34. 34 @leastprivilege Proof of Possession using MTLS • OAuth 2.0

    Mutual-TLS Client Authentication and Certificate-Bound Access Tokens – https://tools.ietf.org/html/rfc8705 • Covers both authentication and Proof-of-Possession
  35. 35 @leastprivilege Mutual TLS TLS Tunnel server certificate client certificate

    • authentication • proving to know a secret (private key) • validate chain of trust (optional) • negotiate key material • sign & encrypt traffic
  36. 36 @leastprivilege Sender Constrained Access Tokens w/ MTLS { "iss":

    "https://issuer", "exp": 1340819380, "nbf": 1340818761, "client_id": "182jmm199", "scope": "api1", "cnf": { "x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2" } }
  37. 37 @leastprivilege Creating an X.509 Client Certificate static X509Certificate2 CreateClientCertificate(string

    name) { var distinguishedName = new X500DistinguishedName($"CN={name}"); using (var rsa = RSA.Create(2048)) { var request = new CertificateRequest( distinguishedName, rsa, HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add( new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); request.CertificateExtensions.Add( new X509EnhancedKeyUsageExtension( new OidCollection { new Oid("1.3.6.1.5.5.7.3.2") }, false)); return request.CreateSelfSigned( new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(10))); } }
  38. 38 @leastprivilege Setting a Client Certificate static SocketsHttpHandler GetHandler(X509Certificate2 certificate)

    { var handler = new SocketsHttpHandler(); handler.SslOptions.ClientCertificates = new X509CertificateCollection { certificate }; return handler; }
  39. 39 @leastprivilege Calling the Token Endpoint static async Task<TokenResponse> RequestTokenAsync()

    { var client = new HttpClient(GetHandler()); var disco = await client.GetDiscoveryDocumentAsync("https://identityserver.local"); var endpoint = disco .TryGetValue(OidcConstants.Discovery.MtlsEndpointAliases) .Value<string>(OidcConstants.Discovery.TokenEndpoint) .ToString(); var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = endpoint, ClientId = "mtls", Scope = "api1" }); return response; }
  40. 40 @leastprivilege MTLS Endpoints • Sub-path • Sub-domain • Separate

    domain https://identityserver.io/connect/mtls/* https://mtls.identityserver.io/* https://identityserver-mtls.io/*
  41. 41 @leastprivilege Server Metadata • Endpoint aliases specify the URLs

    of the MTLS versions of the standard endpoints
  42. 42 @leastprivilege Choice of Web Server / Proxy • IIS

    – supports path-based endpoints – PKI method only • Azure – supports path-based endpoints (but only via exclusion paths) • Kestrel – does not support any isolation • Nginx – supports domain/sub-domain isolation – supports PKI and self-signed
  43. 43 @leastprivilege Example: Sub-domains with Nginx server { listen 443

    ssl; server_name identityserver.io; location / { proxy_pass http://localhost:5000; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 443 ssl; server_name mtls.identityserver.local; ssl_verify_client optional_no_ca; location /connect { proxy_pass http://localhost:5000; proxy_set_header X-SSL-CERT $ssl_client_escaped_cert; } } ASP.NET Core self-signed forward certificate on header
  44. 44 @leastprivilege Reading Certificate from Header public void ConfigureServices(IServiceCollection services)

    { services.AddCertificateForwarding(options => { options.CertificateHeader = "X-SSL-CERT"; options.HeaderConverter = (headerValue) => { X509Certificate2 clientCertificate = null; if(!string.IsNullOrWhiteSpace(headerValue)) { byte[] bytes = Encoding.UTF8.GetBytes(Uri.UnescapeDataString(headerValue)); clientCertificate = new X509Certificate2(bytes); } return clientCertificate; }; }); } public void Configure(IApplicationBuilder app) { app.UseCertificateForwarding(); }
  45. 45 @leastprivilege Certificate Authentication Handler public void ConfigureServices(IServiceCollection services) {

    services.AddAuthentication() .AddCertificate(options => { options.AllowedCertificateTypes = CertificateTypes.All; options.RevocationMode = X509RevocationMode.NoCheck; }); }
  46. 46 @leastprivilege Enabling MTLS in IdentityServer var builder = services.AddIdentityServer(options

    => { options.MutualTls.Enabled = true; // sets "mtls" sub-domain options.MutualTls.DomainName = "mtls"; }); builder.AddMutualTlsSecretValidators();
  47. 47 @leastprivilege Verifying Access Token Ownership at Resource • Resource

    server needs similar setup – accept/validate client certificates – forwarding of certificates to application host • After normal access token validation – compare certificate thumbprint of TLS channel with cnf claim in access token – if they match, caller can prove that it has access to the same key material • proof-of-possession
  48. 48 @leastprivilege Pipeline Overview public void ConfigureServices(IServiceCollection services) { services.AddAuthentication("jwt")

    .AddJwtBearer("jwt", options => { … }) .AddCertificate(options => { … }); services.AddCertificateForwarding(…) } public void Configure(IApplicationBuilder app) { app.UseForwardedHeaders(…) app.UseCertificateForwarding(); app.UseAuthentication(); app.UseConfirmationValidation(…) app.UseAuthorization(); }
  49. 49 @leastprivilege Summary • Asymmetric keys are easier to manage

    and secure than shared secrets • Private key JWT – allows securing both front- and back-channel with single key pair • TLS client certificates – allows binding access token to client – even for public clients