Example #1
0
        private Task SetInvalidContext(CertificateValidatedContext context, string errorMsg)
        {
            context.Fail(new Exception(errorMsg));

            _logger.LogError(errorMsg);
            return(Task.CompletedTask);
        }
Example #2
0
        /// <summary>
        /// Try to validate the incoming client certificate against the server root cert
        /// </summary>
        public Task IsValid(CertificateValidatedContext context)
        {
            bool isValid = false;

            if (!File.Exists(_certConfig.CrtPath))
            {
                return(SetInvalidContext(context, "Invalid path, the public root certificate could not be found " + _certConfig.CrtPath));
            }

            using X509Certificate2 serverCert = new X509Certificate2(_certConfig.CrtPath);
            using X509Chain chain             = new X509Chain();
            chain.ChainPolicy.RevocationMode  = X509RevocationMode.NoCheck;
            try
            {
                if (!chain.Build(context.ClientCertificate))
                {
                    return(SetInvalidContext(context, "Building the client certificate has failed."));
                }

                // Validate only certificates that are issued by our private CA
                isValid = chain.ChainElements.Cast <X509ChainElement>().Any(x => x.Certificate.Thumbprint == serverCert.Thumbprint);
                if (!isValid)
                {
                    return(SetInvalidContext(context, "Chain of trust certification failed."));
                }
                context.Success();
                return(Task.CompletedTask);
            }
            catch (Exception _ex)
            {
                return(SetInvalidContext(context, _ex.Message));
            }
        }
    private async Task <AuthenticateResult> ValidateCertificateAsync(X509Certificate2 clientCertificate)
    {
        var isCertificateSelfSigned = clientCertificate.IsSelfSigned();

        // If we have a self signed cert, and they're not allowed, exit early and not bother with
        // any other validations.
        if (isCertificateSelfSigned &&
            !Options.AllowedCertificateTypes.HasFlag(CertificateTypes.SelfSigned))
        {
            Logger.CertificateRejected("Self signed", clientCertificate.Subject);
            return(AuthenticateResult.Fail("Options do not allow self signed certificates."));
        }

        // If we have a chained cert, and they're not allowed, exit early and not bother with
        // any other validations.
        if (!isCertificateSelfSigned &&
            !Options.AllowedCertificateTypes.HasFlag(CertificateTypes.Chained))
        {
            Logger.CertificateRejected("Chained", clientCertificate.Subject);
            return(AuthenticateResult.Fail("Options do not allow chained certificates."));
        }

        var chainPolicy = BuildChainPolicy(clientCertificate, isCertificateSelfSigned);

        using var chain = new X509Chain
              {
                  ChainPolicy = chainPolicy
              };

        var certificateIsValid = chain.Build(clientCertificate);

        if (!certificateIsValid)
        {
            var chainErrors = new List <string>(chain.ChainStatus.Length);
            foreach (var validationFailure in chain.ChainStatus)
            {
                chainErrors.Add($"{validationFailure.Status} {validationFailure.StatusInformation}");
            }
            Logger.CertificateFailedValidation(clientCertificate.Subject, chainErrors);
            return(AuthenticateResult.Fail("Client certificate failed validation."));
        }

        var certificateValidatedContext = new CertificateValidatedContext(Context, Scheme, Options)
        {
            ClientCertificate = clientCertificate,
            Principal         = CreatePrincipal(clientCertificate)
        };

        await Events.CertificateValidated(certificateValidatedContext);

        if (certificateValidatedContext.Result != null)
        {
            return(certificateValidatedContext.Result);
        }

        certificateValidatedContext.Success();
        return(certificateValidatedContext.Result !);
    }
        private bool TryResolveIdentityAltSecurityIdentities(CertificateValidatedContext context, ClaimsIdentity user)
        {
            string subject      = context.ClientCertificate.SubjectName.ToCommaSeparatedString();
            string issuer       = context.ClientCertificate.IssuerName.ToCommaSeparatedString();
            string serialNumber = context.ClientCertificate.GetSerialNumber().ToHexString();
            string ski          = context.ClientCertificate.Extensions.OfType <X509SubjectKeyIdentifierExtension>().FirstOrDefault()?.SubjectKeyIdentifier;
            string sha1keyhash  = SHA1.Create().ComputeHash(context.ClientCertificate.GetPublicKey()).ToHexString();
            string rfc822       = context.ClientCertificate.GetNameInfo(X509NameType.EmailName, false);

            List <string> altSecurityIdentities = new List <string>();

            if (!string.IsNullOrWhiteSpace(issuer) && !string.IsNullOrWhiteSpace(subject))
            {
                altSecurityIdentities.Add($"X509:<I>{issuer}<S>{subject}");
            }

            if (!string.IsNullOrWhiteSpace(subject) && this.options.ValidationMethod == ClientCertificateValidationMethod.NtAuthStore)
            {
                altSecurityIdentities.Add($"X509:<S>{subject}");
            }

            if (!string.IsNullOrWhiteSpace(issuer) && !string.IsNullOrWhiteSpace(serialNumber))
            {
                altSecurityIdentities.Add($"X509:<I>{issuer}<SR>{serialNumber}");
            }

            if (!string.IsNullOrWhiteSpace(ski))
            {
                altSecurityIdentities.Add($"X509:<SKI>{ski}");
            }

            if (!string.IsNullOrWhiteSpace(sha1keyhash))
            {
                altSecurityIdentities.Add($"X509:<SHA1-PUKEY>{sha1keyhash}");
            }

            if (!string.IsNullOrWhiteSpace(rfc822) && this.options.ValidationMethod == ClientCertificateValidationMethod.NtAuthStore)
            {
                altSecurityIdentities.Add($"X509:<RFC822>{rfc822}");
            }

            foreach (string altSecurityIdentity in altSecurityIdentities)
            {
                this.logger.LogTrace("Attempting to find user with altSecurityIdentity {altSecurityIdentity}", altSecurityIdentity);

                if (this.directory.TryGetUserByAltSecurityIdentity(altSecurityIdentity, out IUser u))
                {
                    this.logger.LogTrace("Found user {user} with altSecurityIdentity {altSecurityIdentity}", u.MsDsPrincipalName, altSecurityIdentity);
                    user.AddClaim(new Claim(ClaimTypes.PrimarySid, u.Sid.ToString(), context.Options.ClaimsIssuer));
                    this.AddAuthZClaims(u, user);
                    return(true);
                }
            }

            return(false);
        }
        private bool TryResolveIdentityUpnSan(CertificateValidatedContext context, ClaimsIdentity user, string upn)
        {
            if (this.directory.TryGetUser(upn, out IUser u))
            {
                this.logger.LogTrace("Found user {user} with upn {upn}", u.MsDsPrincipalName, upn);
                user.AddClaim(new Claim(ClaimTypes.PrimarySid, u.Sid.ToString(), context.Options.ClaimsIssuer));
                this.AddAuthZClaims(u, user);
                return(true);
            }

            return(false);
        }
        private void ResolveIdentity(CertificateValidatedContext context)
        {
            ClaimsIdentity user = context.Principal.Identity as ClaimsIdentity;

            this.logger.LogTrace("Attempting to resolve certificate identity. Resolution mode: {mode}", this.options.IdentityResolutionMode);

            if ((this.options.IdentityResolutionMode == CertificateIdentityResolutionMode.Default ||
                 this.options.IdentityResolutionMode.HasFlag(CertificateIdentityResolutionMode.UpnSan)))
            {
                this.logger.LogTrace("Attempting identity resolution using UPN");
                string upn = user?.FindFirst(ClaimTypes.Upn)?.Value;

                if (upn == null)
                {
                    string warning = $"The certificate for subject '{context.ClientCertificate.Subject}' did not contain an subject alternative name containing the user's UPN";
                    if (!this.options.IdentityResolutionMode.HasFlag(CertificateIdentityResolutionMode.AltSecurityIdentities))
                    {
                        throw new CertificateIdentityNotFoundException(warning);
                    }
                    else
                    {
                        this.logger.LogWarning(warning);
                    }
                }
                else
                {
                    this.logger.LogTrace("UPN value found {upn}", upn);

                    if (this.TryResolveIdentityUpnSan(context, user, upn))
                    {
                        return;
                    }
                }
            }

            if (this.options.IdentityResolutionMode.HasFlag(CertificateIdentityResolutionMode.AltSecurityIdentities))
            {
                this.logger.LogTrace("Attempting identity resolution using altSecurityIdentities");

                if (this.TryResolveIdentityAltSecurityIdentities(context, user))
                {
                    return;
                }
            }

            string message = string.Format(LogMessages.UserNotFoundInDirectory, user.ToClaimList());

            throw new CertificateIdentityNotFoundException(message);
        }
Example #7
0
        public Task CertificateValidated(CertificateValidatedContext context)
        {
            // Check subject or thumbprint
            if (context.ClientCertificate.Subject !=
                "CN=cloudauth-apim.azure-api.net, O=CloudAuthority, C=AU")
            {
                context.Fail("Subject is unexpected");
            }
            else
            {
                context.Success();
            }

            return(Task.CompletedTask);
        }
        public Task Handle(CertificateValidatedContext context, ClientCertificate clientCertificate)
        {
            if (IsValidCertificate(clientCertificate))
            {
                var claims = new[] {
                    new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer),
                    new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer)
                };
                context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
                context.Success();
            }
            else
            {
                context.Fail("Invalid Client Certificate");
            }

            return(Task.CompletedTask);
        }
Example #9
0
        public override Task CertificateValidated(CertificateValidatedContext context)
        {
            _logger.LogTrace($"Initial cert validation successful, entering {nameof(CertificateValidated)}()");
            X509Certificate2 cert = context.ClientCertificate;

            using (var chain = new X509Chain())
            {
                chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
                chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EndCertificateOnly;
                chain.Build(cert);

                // 0th element is our leaf cert
                // 1st element must contain the issuer's cert
                var caCert = chain.ChainElements[1].Certificate;

                // For now, assume the DB is keyed on whatever gets entered into the cert subject field, whether thats an UID, email address, or something else entirely.
                string key  = cert.GetNameInfo(X509NameType.SimpleName, false);
                User   user = _dbContext.Users.Find(key);

                if (user == null)
                {
                    _logger.LogError($"Cert auth failed: could not find a user with UID '{key}'");
                    context.NoResult();
                    return(Task.CompletedTask);
                }

                var userClaims = user.ToClaimsIdentity(context.Scheme.Name);

                // Attach a flag to the user identity to easily reveal the "Admin" navigation tab in the view
                if (_dbContext.Admins.Find(key) != null)
                {
                    userClaims.AddClaim(new Claim(Constants.AdminClaim, true.ToString()));
                }

                context.Principal.AddIdentity(userClaims);

                _logger.LogDebug($"Cert auth success (serial no: {cert.SerialNumber})");

                context.Success();

                return(Task.CompletedTask);
            }
        }
Example #10
0
        private void ResolveIdentity(CertificateValidatedContext context)
        {
            ClaimsIdentity user = context.Principal.Identity as ClaimsIdentity;
            Claim          c    = user?.FindFirst(ClaimTypes.Upn);

            if (c?.Value == null)
            {
                string message = $"The certificate for subject '{context.ClientCertificate.Subject}' did not contain an alternate subject name containing the user's UPN";
                throw new CertificateIdentityNotFoundException(message);
            }

            if (!this.directory.TryGetUser(c.Value, out IUser u))
            {
                string message = string.Format(LogMessages.UserNotFoundInDirectory, user.ToClaimList());
                throw new CertificateIdentityNotFoundException(message);
            }

            user.AddClaim(new Claim(ClaimTypes.PrimarySid, u.Sid.ToString(), context.Options.ClaimsIssuer));
            this.AddAuthZClaims(u, user);
        }
Example #11
0
        private void ValidateCertificate(CertificateValidatedContext context)
        {
            X509Chain chain = new X509Chain
            {
                ChainPolicy = this.BuildChainPolicy()
            };

            if (!chain.Build(context.ClientCertificate))
            {
                throw new CertificateValidationException("The certificate did not contain the required EKUs");
            }

            if (!this.ValidateIssuer(chain))
            {
                throw new CertificateValidationException("One of the required issuers was not found in the certificate chain");
            }

            if (!this.ValidateNtAuthStore(chain))
            {
                throw new CertificateValidationException("The certificate chain did not validate to an issuer that is trusted by the enterprise to issue smart card certificates");
            }
        }
Example #12
0
        private static void PerformValidation(CertificateValidatedContext context)
        {
            string subject = context.ClientCertificate.Subject;
            string pid;
            int    closing = subject.LastIndexOf(')');
            int    opening = subject.LastIndexOf('(', closing - 1);

            if (opening <= -1)
            {
                context.Fail("No PID provided.");
            }

            pid = subject.Substring(opening + 1, closing - opening - 1);

            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier, pid, ClaimValueTypes.String, context.Options.ClaimsIssuer)
            };

            context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
            context.Success();
        }
 private static ClientCertificate GetClientCertificate(this CertificateValidatedContext context)
 {
     return(new ClientCertificate(context.ClientCertificate));
 }
Example #14
0
        protected override async Task <AuthenticateResult> HandleAuthenticateAsync()
        {
            // You only get client certificates over HTTPS
            if (!Context.Request.IsHttps)
            {
                return(AuthenticateResult.NoResult());
            }

            try
            {
                var clientCertificate = await Context.Connection.GetClientCertificateAsync();

                // This should never be the case, as cert authentication happens long before ASP.NET kicks in.
                if (clientCertificate == null)
                {
                    Logger.NoCertificate();
                    return(AuthenticateResult.NoResult());
                }

                // If we have a self signed cert, and they're not allowed, exit early and not bother with
                // any other validations.
                if (clientCertificate.IsSelfSigned() && !Options.AllowedCertificateTypes.HasFlag(CertificateTypes.SelfSigned))
                {
                    Logger.LogWarning("Self signed certificate rejected, subject was {0}", clientCertificate.Subject);
                    return(AuthenticateResult.Fail("Options do not allow self signed certificates."));
                }

                // If we have a chained cert, and they're not allowed, exit early and not bother with
                // any other validations.
                if (!clientCertificate.IsSelfSigned() && !Options.AllowedCertificateTypes.HasFlag(CertificateTypes.Chained))
                {
                    Logger.CertificateRejected("Chained", clientCertificate.Subject);
                    return(AuthenticateResult.Fail("Options do not allow chained certificates."));
                }

                var chainPolicy = BuildChainPolicy(clientCertificate);
                var chain       = new X509Chain
                {
                    ChainPolicy = chainPolicy
                };

                //// <variation>
                var certificateIsValid = IsChainValid(chain, clientCertificate);
                //// </variation>

                if (!certificateIsValid)
                {
                    var chainErrors = new List <string>();
                    foreach (var validationFailure in chain.ChainStatus)
                    {
                        chainErrors.Add($"{validationFailure.Status} {validationFailure.StatusInformation}");
                    }

                    Logger.CertificateFailedValidation(clientCertificate.Subject, chainErrors);
                    return(AuthenticateResult.Fail("Client certificate failed validation."));
                }

                var certificateValidatedContext = new CertificateValidatedContext(Context, Scheme, Options)
                {
                    ClientCertificate = clientCertificate,
                    Principal         = CreatePrincipal(clientCertificate)
                };

                await Events.CertificateValidated(certificateValidatedContext);

                if (certificateValidatedContext.Result != null)
                {
                    return(certificateValidatedContext.Result);
                }

                certificateValidatedContext.Success();
                return(certificateValidatedContext.Result);
            }
            catch (Exception ex)
            {
                var authenticationFailedContext = new CertificateAuthenticationFailedContext(Context, Scheme, Options)
                {
                    Exception = ex
                };

                await Events.AuthenticationFailed(authenticationFailedContext);

                if (authenticationFailedContext.Result != null)
                {
                    return(authenticationFailedContext.Result);
                }

                throw;
            }
        }
Example #15
0
 /// <summary>
 /// Invoked after a certificate has been validated
 /// </summary>
 /// <param name="context"></param>
 /// <returns></returns>
 public virtual Task CertificateValidated(CertificateValidatedContext context) => OnCertificateValidated(context);
Example #16
0
 private static Task Validator(CertificateValidatedContext context)
 {
     PerformValidation(context);
     return(Task.CompletedTask);
 }