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()); }