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