Exemple #1
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);
        }
        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));
        }
        /// <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);
        }
        /// <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);
        }
        /// <summary>
        /// Executes before the validation stage and can be used for initial decoding of the message.
        /// </summary>
        /// <param name="preauth">The current context of the request</param>
        public override void PreValidate(PreAuthenticationContext preauth)
        {
            if (preauth == null)
            {
                throw new ArgumentNullException(nameof(preauth));
            }

            ExtractApReq(preauth);

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

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

            state.DecryptedApReq = this.DecryptApReq(state.ApReq, preauth.EvidenceTicketKey);
        }
        /// <summary>
        /// Executes the primary validation process for the pre-auth data.
        /// </summary>
        /// <param name="asReq">The message to validate</param>
        /// <param name="context">The current context of the request</param>
        /// <returns>Optionally returns PA-Data that needs to be sent to the client otherwise returns null.</returns>
        public override KrbPaData Validate(KrbKdcReq asReq, PreAuthenticationContext context)
        {
            // First we authenticate the incoming request
            //
            // 1. Get the ApReq (TGT) from the PA-Data of the request
            // 2. Decrypt the TGT and extract the client calling identity

            if (context.EvidenceTicketIdentity == null)
            {
                // we wouldn't ever hit this in the normal case, but we could hit it
                // if a referral came in from a realm we don't recognize or don't trust

                throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_S_PRINCIPAL_UNKNOWN);
            }

            var evidenceSName = KrbPrincipalName.FromPrincipal(context.EvidenceTicketIdentity, PrincipalNameType.NT_SRV_INST);

            if (!evidenceSName.IsKrbtgt())
            {
                // spec-wise this isn't exactly correct as the authz ticket might be for renewal
                // we will deviate from the spec because that's how other KDCs operate today
                // KDC_ERR_PADATA_TYPE_NOSUPP is the closest error to indicate the way you
                // authenticated the request is not something we're willing to accept

                throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_PADATA_TYPE_NOSUPP);
            }

            // we need to validate that the evidence ticket is the KDC service (krbtgt)
            // it might either be our KDC that issued it, or a KDC from another realm (referral)
            // if it's ours it'll match our service name (krbtgt), and it'll decrypt with our key
            // if it's a referral it'll match a trusted realm's name and decrypt with their key
            // if it's a referral then the incoming identity will also need to be transposed

            // no matter what we only ever want the TGS service ticket
            // it might belong to another realm, but that's ok because it could be a referral

            // it is a krbtgt service identity we recognize
            // it could be ours, or a referral from a trusted realm
            // in either case we can and will validate the ticket and
            // extract the user principal from within the krbtgt ticket

            var krbtgtKey = context.EvidenceTicketIdentity.RetrieveLongTermCredential();

            if (krbtgtKey == null)
            {
                // since the key comes from caller-implemented code we
                // should check to make sure they gave us a usable key

                throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_ETYPE_NOSUPP);
            }

            if (context.EvidenceTicketKey == null)
            {
                context.EvidenceTicketKey = krbtgtKey;
            }

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

            if (state.DecryptedApReq == null)
            {
                state.DecryptedApReq = DecryptApReq(state.ApReq, context.EvidenceTicketKey);
            }

            context.EncryptedPartKey = state.DecryptedApReq.SessionKey;
            context.Ticket           = state.DecryptedApReq.Ticket;

            return(null);
        }
        public override ReadOnlyMemory <byte> ExecuteCore(PreAuthenticationContext context)
        {
            // Now that we know who is requesting the ticket we can issue the ticket
            //
            // 3. Find the requested service principal
            // 4. Determine if the requested service principal is in another realm and if so refer them
            // 5. Evaluate whether the client identity should get a ticket to the service
            // 6. Evaluate whether it should do U2U and if so extract that key instead
            // 7. Generate a service ticket for the calling client to the service
            // 8. return to client

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

            var tgsReq = (KrbTgsReq)context.Message;

            if (context.ServicePrincipal == null)
            {
                // we can't find what they're asking for, but maybe it's in a realm we can transit?

                context.ServicePrincipal = this.ProposeTransitedRealm(tgsReq, context);
            }

            if (context.ServicePrincipal == null)
            {
                // we have no idea what service they're asking for and
                // there isn't a realm we can refer them to that can issue a ticket

                return(GenerateError(
                           KerberosErrorCode.KDC_ERR_S_PRINCIPAL_UNKNOWN,
                           string.Empty,
                           tgsReq.Body.Realm,
                           tgsReq.Body.SName.FullyQualifiedName
                           ));
            }

            // renewal is an odd case here because the SName will be krbtgt
            // does this need to be validated more than the Decrypt call?

            this.EvaluateSecurityPolicy(context.Principal, context.ServicePrincipal);

            KerberosKey serviceKey;

            if (tgsReq.Body.KdcOptions.HasFlag(KdcOptions.EncTktInSkey))
            {
                serviceKey = GetUserToUserTicketKey(tgsReq.Body.AdditionalTickets, context);
            }
            else
            {
                serviceKey = context.ServicePrincipal.RetrieveLongTermCredential();
            }

            var now = this.RealmService.Now();

            TicketFlags flags = 0;

            if (tgsReq.Body.KdcOptions.HasFlag(KdcOptions.Forwardable))
            {
                flags |= TicketFlags.Forwardable;
            }

            if (context.Ticket.Flags.HasFlag(TicketFlags.PreAuthenticated))
            {
                flags |= TicketFlags.PreAuthenticated;
            }

            if (context.IncludePac == null)
            {
                context.IncludePac = DetectPacRequirement(tgsReq);

                if (context.IncludePac == null)
                {
                    context.IncludePac = context.Ticket?.AuthorizationData?.Any(a => a.Type == AuthorizationDataType.AdIfRelevant) ?? false;
                }
            }

            var rst = new ServiceTicketRequest
            {
                KdcAuthorizationKey   = context.EvidenceTicketKey,
                Principal             = context.Principal,
                EncryptedPartKey      = context.EncryptedPartKey,
                EncryptedPartEType    = context.EncryptedPartEType,
                ServicePrincipal      = context.ServicePrincipal,
                ServicePrincipalKey   = serviceKey,
                RealmName             = tgsReq.Body.Realm,
                Addresses             = tgsReq.Body.Addresses,
                RenewTill             = context.Ticket.RenewTill,
                StartTime             = tgsReq.Body.From ?? DateTimeOffset.MinValue,
                EndTime               = tgsReq.Body.Till,
                MaximumTicketLifetime = this.RealmService.Settings.SessionLifetime,
                Flags                = flags,
                Now                  = now,
                Nonce                = tgsReq.Body.Nonce,
                IncludePac           = context.IncludePac ?? false,
                PreferredClientEType = GetPreferredEType(
                    tgsReq.Body.EType,
                    this.RealmService.Configuration.Defaults.PermittedEncryptionTypes,
                    this.RealmService.Configuration.Defaults.AllowWeakCrypto
                    ),
                Compatibility = this.RealmService.Settings.Compatibility,
            };

            if (tgsReq.Body.KdcOptions.HasFlag(KdcOptions.Canonicalize))
            {
                rst.SamAccountName = context.GetState <TgsState>(PaDataType.PA_TGS_REQ).DecryptedApReq.Ticket.CName.FullyQualifiedName;
            }

            // this is set here instead of in GenerateServiceTicket because GST is used by unit tests to
            // generate tickets with weird lifetimes for scenario testing and we don't want to break that

            rst.ClampLifetime();

            var tgsRep = KrbKdcRep.GenerateServiceTicket <KrbTgsRep>(rst);

            return(tgsRep.EncodeApplication());
        }