public async Task Invoke(HttpContext context) { var requestUri = new Uri($"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}"); var requestId = Guid.NewGuid().ToString("D"); bool isForm = false; if (context.Request.Method == HttpMethods.Post && context.Request.HasFormContentType) { _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Request: {context.Request.Method} {requestUri.OriginalString} Form: {string.Join("|", context.Request.Form.Keys)}"); isForm = true; } else { _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Request: {context.Request.Method} {requestUri.OriginalString}"); } if (isForm) { // https://docs.docker.com/registry/spec/auth/oauth/ var service = context.Request.Form["service"].OneOrDefault(); var scope = context.Request.Form["scope"].OneOrDefault(); var accessType = context.Request.Form["access_type"].OneOrDefault(); var includeRefreshToken = string.Equals("offline", accessType, StringComparison.Ordinal); if (!string.IsNullOrEmpty(service)) { if (string.Equals("refresh_token", context.Request.Form["grant_type"], StringComparison.Ordinal)) { var refreshToken = context.Request.Form["refresh_token"].OneOrDefault(); if (!string.IsNullOrEmpty(refreshToken)) { //var ecdsa = ECDsa.Create(); //ecdsa.ImportPkcs8PrivateKey(_settings.TokenAuthentication.EccKey, out _); //var parameters = new TokenValidationParameters { // LifetimeValidator = (before, expires, token, parameters) => expires > DateTime.UtcNow, // ValidateAudience = false, // ValidateIssuer = false, // ValidateActor = false, // ValidateLifetime = true, // IssuerSigningKey = new ECDsaSecurityKey(ecdsa) //}; //var handler = new JwtSecurityTokenHandler(); //var identity = handler.ValidateToken(refreshToken, parameters, out var token); var tokenParts = refreshToken.Split('.'); if (tokenParts.Length == 3) { var header = CommonUtility.Base64UrlDecode(tokenParts[0]); var body = CommonUtility.Base64UrlDecode(tokenParts[1]); var signature = CommonUtility.Base64UrlDecode(tokenParts[2]); var headerObject = JsonSerializer.Deserialize <JwtHeader>(header); if (headerObject == null || headerObject.Algorithm?.StartsWith("ES", StringComparison.Ordinal) != true || !string.Equals("JWT", headerObject.TokenType)) { _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Invalid token received"); } else { var toVerify = Encoding.UTF8.GetBytes(tokenParts[0] + "." + tokenParts[1]); bool valid = false; using (var ecdsa = ECDsa.Create()) { ecdsa.ImportPkcs8PrivateKey(_settings.TokenAuthentication.EccKey, out _); if (headerObject.Algorithm == "ES256") { valid = ecdsa.VerifyData(toVerify, signature, HashAlgorithmName.SHA256); } else if (headerObject.Algorithm == "ES384") { valid = ecdsa.VerifyData(toVerify, signature, HashAlgorithmName.SHA384); } else if (headerObject.Algorithm == "ES512") { valid = ecdsa.VerifyData(toVerify, signature, HashAlgorithmName.SHA512); } } if (!valid) { _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Invalid token received"); } else { var tokenInfo = ReadTokenBody(body); var username = tokenInfo.claims.OneOrDefault(z => z.Type == ClaimsIdentity.DefaultNameClaimType); if (username != null) { var account = _settings.TokenAuthentication.Accounts.FirstOrDefault(z => string.Equals(z.Registry, service, StringComparison.OrdinalIgnoreCase) && string.Equals(z.Username, username.Value, StringComparison.OrdinalIgnoreCase)); if (account != null) { await IssueToken(context, requestId, account, scope.Split(' ').ToList(), includeRefreshToken, refreshToken, true); return; } } } } } } } else if (string.Equals("password", context.Request.Form["grant_type"], StringComparison.Ordinal)) { var username = context.Request.Form["username"].OneOrDefault(); var password = context.Request.Form["password"].OneOrDefault(); var account = _settings.TokenAuthentication.Accounts.FirstOrDefault(z => string.Equals(z.Registry, service, StringComparison.OrdinalIgnoreCase) && string.Equals(z.Username, username, StringComparison.OrdinalIgnoreCase) && string.Equals(z.Password, password, StringComparison.Ordinal)); if (account != null) { await IssueToken(context, requestId, account, scope.Split(' ').ToList(), includeRefreshToken, null, true); return; } else { _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Invalid credentials received"); } } } } else { // https://docs.docker.com/registry/spec/auth/token/ string authorization = context.Request.Headers["Authorization"]; string service = context.Request.Query["service"]; string offline_token = context.Request.Query["offline_token"]; if (authorization != null && authorization.StartsWith("Basic ", StringComparison.Ordinal) && !string.IsNullOrEmpty(service)) { var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(authorization.Substring(6))); int x = credentials.IndexOf(':'); if (x > 0) { string user = credentials.Substring(0, x); string password = credentials.Substring(x + 1); var account = _settings.TokenAuthentication.Accounts.FirstOrDefault(z => string.Equals(z.Registry, service, StringComparison.OrdinalIgnoreCase) && string.Equals(z.Username, user, StringComparison.OrdinalIgnoreCase) && string.Equals(z.Password, password, StringComparison.Ordinal)); if (account != null) { var includeRefreshToken = string.Equals("true", offline_token, StringComparison.Ordinal); await IssueToken(context, requestId, account, context.Request.Query["scope"].ToList(), includeRefreshToken, null, false); return; } else { _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Invalid credentials received"); } } } } context.Response.StatusCode = 401; context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"Docker token authentication\", charset=\"UTF-8\""; _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Request authorization required"); return; //await _nextMiddleware(context); }