Example #1
0
        public override void PreValidate(PreAuthenticationContext preauth)
        {
            if (preauth == null)
            {
                throw new ArgumentNullException(nameof(preauth));
            }

            var asReq = (KrbKdcReq)preauth.Message;

            var paPk = asReq.PaData.FirstOrDefault(p => p.Type == PaDataType.PA_PK_AS_REQ);

            if (paPk == null)
            {
                return;
            }

            var pkreq = KrbPaPkAsReq.Decode(paPk.Value);

            var signedCms = new SignedCms();

            signedCms.Decode(pkreq.SignedAuthPack.ToArray());

            var state = new PkInitState
            {
                PkInitRequest = pkreq,
                Cms           = signedCms
            };

            state.ClientCertificate.AddRange(signedCms.Certificates);

            preauth.PreAuthenticationState[PaDataType.PA_PK_AS_REQ] = state;
        }
        protected virtual IKerberosPrincipal TransformClientIdentity(PreAuthenticationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var state = context.GetState <TgsState>(PaDataType.PA_TGS_REQ);

            if (state.DecryptedApReq == null)
            {
                throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_BADOPTION);
            }

            // now that we have the identity from the ticket we duplicate all its interesting bits
            // to include in the requested service ticket but excluding any potentially dangerous
            // authz values if it's from a referred realm

            if (context.EvidenceTicketIdentity.Type == PrincipalType.TrustedDomain)
            {
                // it's a referral from another realm so all the copy and filter
                // goop is in TransitedKerberosPrincipal.GeneratePac()

                return(new TransitedKerberosPrincipal(state.DecryptedApReq));
            }

            // TODO: shouldn't be calling Find(), but instead copying the existing PAC
            // This won't need an async call because it'll eventually go away

            return(this.RealmService.Principals.Find(state.DecryptedApReq.Ticket.CName, state.DecryptedApReq.Ticket.CRealm));
        }
        private async Task <IEnumerable <KrbPaData> > ProcessPreAuth(PreAuthenticationContext preauth, KrbKdcReq asReq)
        {
            // if there are pre-auth handlers registered check whether they intersect with what the user supports.
            // at some point in the future this should evaluate whether there's at least a m-of-n PA-Data approval
            // this would probably best be driven by some policy check, which would involve coming up with a logic
            // system of some sort. Will leave that as an exercise for future me.

            IEnumerable <PaDataType> invokingAuthTypes = GetOrderedPreAuth(preauth);

            var preAuthRequirements = new List <KrbPaData>();

            foreach (var preAuthType in invokingAuthTypes)
            {
                await InvokePreAuthHandler(asReq, preauth, preAuthRequirements, PreAuthHandlers[preAuthType]);
            }

            // if the pre-auth handlers think auth is required we should check with the
            // post-auth handlers because they may add hints to help the client like if
            // they should use specific etypes or salts.
            //
            // the post-auth handlers will determine if they need to do anything based
            // on their own criteria.

            foreach (var preAuthType in PostProcessAuthHandlers)
            {
                var func = preAuthType.Value;

                var handler = func(RealmService);

                await handler.PostValidate(preauth.Principal, preAuthRequirements);
            }

            return(preAuthRequirements);
        }
Example #4
0
        public override void ValidateTicketRequest(PreAuthenticationContext context)
        {
            ProcessPreAuth(context);

            if (!context.PreAuthenticationSatisfied)
            {
                throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_PADATA_TYPE_NOSUPP);
            }

            var state = context.GetState <TgsState>(PaDataType.PA_TGS_REQ);

            if (state.DecryptedApReq == null)
            {
                throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_BADOPTION);
            }

            // now that we have the identity from the ticket we duplicate all its interesting bits
            // to include in the requested service ticket but excluding any potentially dangerous
            // authz values if it's from a referred realm

            // NOTE: Transform should never be async.
            // It should eventually be a fast transform once the todo is resolved

            var principal = TransformClientIdentity(state.DecryptedApReq, context.EvidenceTicketIdentity);

            // this should never hit since we just copy the principal information from the krbtgt
            // but callers can override TransformClientIdentity so lets fail quickly to be safe

            context.Principal = principal ?? throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_C_PRINCIPAL_UNKNOWN);
        }
        public override void ValidateTicketRequest(PreAuthenticationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            this.ProcessPreAuth(context);

            if (!context.PreAuthenticationSatisfied)
            {
                throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_PADATA_TYPE_NOSUPP);
            }

            if (context.Principal == null)
            {
                context.Principal = this.TransformClientIdentity(context);
            }

            // this should never hit since we just copy the principal information from the krbtgt
            // but callers can override TransformClientIdentity so lets fail quickly to be safe

            if (context.Principal == null)
            {
                throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_C_PRINCIPAL_UNKNOWN);
            }
        }
        protected void InvokeAuthHandlers(
            PreAuthenticationContext preauth,
            IEnumerable <PaDataType> invokingAuthTypes,
            IDictionary <PaDataType, PreAuthHandlerConstructor> handlers,
            Func <KdcPreAuthenticationHandlerBase, KrbKdcReq, PreAuthenticationContext, bool> preauthExec
            )
        {
            if (preauth == null)
            {
                throw new ArgumentNullException(nameof(preauth));
            }

            if (invokingAuthTypes == null)
            {
                throw new ArgumentNullException(nameof(invokingAuthTypes));
            }

            if (preauth.Message is KrbKdcReq asReq)
            {
                foreach (var preAuthType in invokingAuthTypes)
                {
                    var func = handlers[preAuthType];

                    var handler = func(this.RealmService);

                    if (!preauthExec(handler, asReq, preauth))
                    {
                        break;
                    }
                }
            }
        }
Example #7
0
        public override void ValidateTicketRequest(PreAuthenticationContext preauth)
        {
            if (preauth?.Principal == null)
            {
                return;
            }

            try
            {
                var preauthReq = this.ProcessPreAuth(preauth);

                if (preauth.PaData == null)
                {
                    preauth.PaData = Array.Empty <KrbPaData>();
                }

                preauth.PaData = preauth.PaData.Union(preauthReq).ToArray();
            }
            catch (KerberosValidationException kex)
            {
                this.logger.LogWarning(kex, "AS-REQ failed processing for principal {Principal}", preauth.Principal.PrincipalName);

                preauth.Failure = kex;
            }
        }
Example #8
0
        public override async Task <KrbPaData> Validate(KrbKdcReq asReq, PreAuthenticationContext preauth)
        {
            if (preauth.PreAuthenticationSatisfied)
            {
                return(null);
            }

            var principal = preauth.Principal;

            var timestamp = asReq.DecryptTimestamp(await principal.RetrieveLongTermCredential());

            if (timestamp == DateTimeOffset.MinValue)
            {
                return(new KrbPaData
                {
                    Type = PaDataType.PA_ENC_TIMESTAMP
                });
            }

            var skew = Service.Settings.MaximumSkew;

            DateTimeOffset now = Service.Now();

            if (Abs(now - timestamp) > skew)
            {
                throw new KerberosValidationException(
                          $"Timestamp window is greater than allowed skew. Start: {timestamp}; End: {now}; Skew: {skew}"
                          );
            }

            return(null);
        }
        public override ReadOnlyMemory <byte> ExecuteCore(PreAuthenticationContext context)
        {
            // 1. check what pre-auth validation is required for user
            // 2. enumerate all pre-auth handlers that are available
            //      - fail hard if required doesn't intersect available
            // 3. if pre-auth is required and not present, return error prompting for it
            // 4. if pre-auth is present, validate it
            // 5. if pre-auth failed, return error
            // 6. if some pre-auth succeeded, return error
            // 7. if all required validation succeeds, generate PAC, TGT, and return it

            if (context.Failure != null)
            {
                return(PreAuthFailed(context));
            }

            KrbAsReq asReq = (KrbAsReq)context.Message;

            if (context.Principal == null)
            {
                logger.LogInformation("User {User} not found in realm {Realm}", asReq.Body.CName.FullyQualifiedName, RealmService.Name);

                return(GenerateError(KerberosErrorCode.KDC_ERR_C_PRINCIPAL_UNKNOWN, null, RealmService.Name, asReq.Body.CName.FullyQualifiedName));
            }

            if (!context.PreAuthenticationSatisfied)
            {
                return(RequirePreAuth(context));
            }

            return(GenerateAsRep(asReq, context));
        }
        public override void QueryPreValidate(PreAuthenticationContext context)
        {
            KrbAsReq asReq = (KrbAsReq)context.Message;

            context.Principal        = RealmService.Principals.Find(asReq.Body.CName);
            context.ServicePrincipal = RealmService.Principals.Find(KrbPrincipalName.WellKnown.Krbtgt());
        }
Example #11
0
        public override async Task QueryPreExecuteAsync(PreAuthenticationContext context)
        {
            var tgsReq = (KrbTgsReq)context.Message;

            logger.LogInformation("TGS-REQ incoming. SPN = {SPN}", tgsReq.Body.SName.FullyQualifiedName);

            context.ServicePrincipal = await RealmService.Principals.FindAsync(tgsReq.Body.SName);
        }
        public override async Task QueryPreValidateAsync(PreAuthenticationContext context)
        {
            KrbAsReq asReq = (KrbAsReq)context.Message;

            context.Principal = await RealmService.Principals.FindAsync(asReq.Body.CName);

            context.ServicePrincipal = await RealmService.Principals.FindAsync(KrbPrincipalName.WellKnown.Krbtgt());
        }
        protected override IEnumerable <PaDataType> GetOrderedPreAuth(PreAuthenticationContext preauth)
        {
            var keys = PreAuthHandlers.Keys.Intersect(preauth.Principal.SupportedPreAuthenticationTypes);

            keys = keys.OrderBy(k => Array.IndexOf(PreAuthAscendingPriority, k));

            return(keys);
        }
        public virtual void DecodeMessage(PreAuthenticationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            context.Message = this.DecodeMessage(this.message);
        }
Example #15
0
        private static KrbAuthPack ValidateAuthPack(PreAuthenticationContext preauth, PkInitState state)
        {
            state.Cms.CheckSignature(verifySignatureOnly: true);

            preauth.Principal.Validate(state.Cms.Certificates);

            var authPack = KrbAuthPack.Decode(state.Cms.ContentInfo.Content);

            return(authPack);
        }
Example #16
0
        public override async Task QueryPreValidateAsync(PreAuthenticationContext context)
        {
            if (context.EvidenceTicketIdentity != null)
            {
                return;
            }

            var apReq = PaDataTgsTicketHandler.ExtractApReq(context);

            context.EvidenceTicketIdentity = await RealmService.Principals.FindAsync(apReq.Ticket.SName);
        }
Example #17
0
        private ReadOnlyMemory <byte> PreAuthFailed(PreAuthenticationContext context)
        {
            var err = new KrbError
            {
                ErrorCode = KerberosErrorCode.KDC_ERR_PREAUTH_FAILED,
                EText     = context.Failure.Message,
                Realm     = this.RealmService.Name,
                SName     = KrbPrincipalName.FromPrincipal(context.Principal)
            };

            return(err.EncodeApplication());
        }
        /// <summary>
        /// Executes before the validation stage and can be used for initial decoding of the message.
        /// </summary>
        /// <param name="preauth"></param>
        public override void PreValidate(PreAuthenticationContext preauth)
        {
            ExtractApReq(preauth);

            if (preauth.EvidenceTicketKey == null)
            {
                return;
            }

            var state = preauth.GetState <TgsState>(PaDataType.PA_TGS_REQ);

            state.DecryptedApReq = DecryptApReq(state.ApReq, preauth.EvidenceTicketKey);
        }
 public virtual void ExecutePreValidate(PreAuthenticationContext context)
 {
     this.InvokeAuthHandlers(
         context,
         this.PreAuthHandlers.Keys,
         this.PreAuthHandlers,
         (handler, req, ctx) =>
     {
         handler.PreValidate(ctx);
         return(true);
     }
         );
 }
Example #20
0
        public override void QueryPreValidate(PreAuthenticationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            KrbAsReq         asReq      = (KrbAsReq)context.Message;
            KrbPrincipalName krbtgtName = KrbPrincipalName.WellKnown.Krbtgt(asReq.Body.Realm);

            context.Principal        = this.RealmService.Principals.Find(asReq.Body.CName, asReq.Realm);
            context.ServicePrincipal = this.RealmService.Principals.Find(krbtgtName, asReq.Realm);
        }
Example #21
0
        protected override IEnumerable <PaDataType> GetOrderedPreAuth(PreAuthenticationContext preauth)
        {
            if (preauth == null)
            {
                throw new ArgumentNullException(nameof(preauth));
            }

            var keys = this.PreAuthHandlers.Keys.Intersect(preauth.Principal.SupportedPreAuthenticationTypes);

            keys = keys.OrderBy(k => Array.IndexOf(PreAuthAscendingPriority, k));

            return(keys);
        }
Example #22
0
        public override async Task QueryPreValidateAsync(PreAuthenticationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            KrbAsReq         asReq      = (KrbAsReq)context.Message;
            KrbPrincipalName krbtgtName = KrbPrincipalName.WellKnown.Krbtgt(asReq.Body.Realm);

            context.Principal = await this.RealmService.Principals.FindAsync(asReq.Body.CName, asReq.Realm).ConfigureAwait(false);

            context.ServicePrincipal = await this.RealmService.Principals.FindAsync(krbtgtName, asReq.Realm).ConfigureAwait(false);
        }
        /// <summary>
        /// Locate the AP-REQ in the PA-Data of a TGS-REQ.
        /// </summary>
        /// <param name="context">The current contex of the request.</param>
        /// <returns>Returns the AP-REQ message within the TGS-REQ PA-Data.</returns>
        public static KrbApReq ExtractApReq(PreAuthenticationContext context)
        {
            var state = context.GetState <TgsState>(PaDataType.PA_TGS_REQ);

            if (state.ApReq == null)
            {
                var tgsReq = (KrbTgsReq)context.Message;

                var paData = tgsReq.PaData.First(p => p.Type == PaDataType.PA_TGS_REQ);

                state.ApReq = paData.DecodeApReq();
            }

            return(state.ApReq);
        }
        protected virtual IEnumerable <KrbPaData> ProcessPreAuth(PreAuthenticationContext preauth)
        {
            // if there are pre-auth handlers registered check whether they intersect with what the user supports.
            // at some point in the future this should evaluate whether there's at least a m-of-n PA-Data approval
            // this would probably best be driven by some policy check, which would involve coming up with a logic
            // system of some sort. Will leave that as an exercise for future me.

            IEnumerable <PaDataType> invokingAuthTypes = this.GetOrderedPreAuth(preauth);

            var preAuthRequirements = new List <KrbPaData>();

            this.InvokeAuthHandlers(
                preauth,
                invokingAuthTypes,
                this.PreAuthHandlers,
                (handler, req, context) =>
            {
                var preAuthRequirement = handler.Validate(req, context);

                if (preAuthRequirement != null)
                {
                    preAuthRequirements.Add(preAuthRequirement);
                }

                return(!context.PreAuthenticationSatisfied);
            }
                );

            // if the pre-auth handlers think auth is required we should check with the
            // post-auth handlers because they may add hints to help the client like if
            // they should use specific etypes or salts.
            //
            // the post-auth handlers will determine if they need to do anything based
            // on their own criteria.

            this.InvokeAuthHandlers(
                preauth,
                this.PostProcessAuthHandlers.Keys,
                this.PostProcessAuthHandlers,
                (handler, req, context) =>
            {
                handler.PostValidate(context.Principal, preAuthRequirements);
                return(true);
            }
                );

            return(preAuthRequirements);
        }
        public override async Task QueryPreExecuteAsync(PreAuthenticationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var tgsReq = (KrbTgsReq)context.Message;

            this.logger.LogInformation(
                "TGS-REQ incoming. SPN = {SPN}, Realm {REALM}",
                tgsReq.Body.SName.FullyQualifiedName,
                tgsReq.Body.Realm);

            context.ServicePrincipal = await this.RealmService.Principals.FindAsync(tgsReq.Body.SName, tgsReq.Body.Realm).ConfigureAwait(false);
        }
        public override void QueryPreValidate(PreAuthenticationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.EvidenceTicketIdentity != null)
            {
                return;
            }

            var apReq = PaDataTgsTicketHandler.ExtractApReq(context);

            context.EvidenceTicketIdentity = this.RealmService.Principals.Find(apReq.Ticket.SName, apReq.Ticket.Realm);
        }
        public override async Task QueryPreValidateAsync(PreAuthenticationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.EvidenceTicketIdentity != null)
            {
                return;
            }

            var apReq = PaDataTgsTicketHandler.ExtractApReq(context);

            context.EvidenceTicketIdentity = await this.RealmService.Principals.FindAsync(apReq.Ticket.SName).ConfigureAwait(false);
        }
        private async Task InvokePreAuthHandler(
            KrbKdcReq asReq,
            PreAuthenticationContext preauth,
            List <KrbPaData> preAuthRequirements,
            PreAuthHandlerConstructor func
            )
        {
            var handler = func(RealmService);

            var preAuthRequirement = await handler.Validate(asReq, preauth);

            if (preAuthRequirement != null)
            {
                preAuthRequirements.Add(preAuthRequirement);
            }
        }
        protected override async Task <ReadOnlyMemory <byte> > ExecuteCore(IKerberosMessage message)
        {
            // 1. check what pre-auth validation is required for user
            // 2. enumerate all pre-auth handlers that are available
            //      - fail hard if required doesn't intersect available
            // 3. if pre-auth is required and not present, return error prompting for it
            // 4. if pre-auth is present, validate it
            // 5. if pre-auth failed, return error
            // 6. if some pre-auth succeeded, return error
            // 7. if all required validation succeeds, generate PAC, TGT, and return it

            KrbAsReq asReq = (KrbAsReq)message;

            var username = asReq.Body.CName.FullyQualifiedName;

            var principal = await RealmService.Principals.Find(username);

            if (principal == null)
            {
                logger.LogInformation("User {User} not found in realm {Realm}", username, RealmService.Name);

                return(GenerateError(KerberosErrorCode.KDC_ERR_S_PRINCIPAL_UNKNOWN, null, RealmService.Name, username));
            }

            var preauth = new PreAuthenticationContext
            {
                Principal = principal
            };

            try
            {
                var preAuthRequests = await ProcessPreAuth(preauth, asReq);

                if (preAuthRequests.Count() > 0)
                {
                    return(RequirePreAuth(preAuthRequests, principal));
                }
            }
            catch (KerberosValidationException kex)
            {
                logger.LogWarning(kex, "AS-REQ failed processing for principal {Principal}", principal);

                return(PreAuthFailed(kex, principal));
            }

            return(await GenerateAsRep(preauth, asReq));
        }
Example #30
0
        public override KrbPaData Validate(KrbKdcReq asReq, PreAuthenticationContext preauth)
        {
            if (asReq == null)
            {
                throw new ArgumentNullException(nameof(asReq));
            }

            if (preauth == null)
            {
                throw new ArgumentNullException(nameof(preauth));
            }

            if (preauth.PreAuthenticationSatisfied)
            {
                return(null);
            }

            var principal = preauth.Principal;
            var cred      = principal.RetrieveLongTermCredential();

            var timestamp = asReq.DecryptTimestamp(cred, out EncryptionType etype);

            if (timestamp == DateTimeOffset.MinValue)
            {
                return(new KrbPaData
                {
                    Type = PaDataType.PA_ENC_TIMESTAMP
                });
            }

            var skew = this.Service.Settings.MaximumSkew;

            DateTimeOffset now = this.Service.Now();

            if (Abs(now - timestamp) > skew)
            {
                throw new KerberosValidationException(
                          $"Timestamp window is greater than allowed skew. Start: {timestamp}; End: {now}; Skew: {skew}"
                          );
            }

            preauth.EncryptedPartKey   = cred;
            preauth.EncryptedPartEType = etype;
            preauth.ClientAuthority    = PaDataType.PA_ENC_TIMESTAMP;

            return(null);
        }