public override async Task <KrbPaData> Validate(KrbKdcReq asReq, IKerberosPrincipal 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 ((now - timestamp) > skew) { throw new KerberosValidationException( $"Timestamp window is greater than allowed skew. Start: {timestamp}; End: {now}; Skew: {skew}" ); } return(null); }
public override async Task PostValidate(IKerberosPrincipal principal, List <KrbPaData> preAuthRequirements) { if (preAuthRequirements.Count <= 0) { // we don't want to include this if nothing is required otherwise we could // trigger a pre-auth check later in the flow or by the client in response return; } var cred = await principal.RetrieveLongTermCredential(); var etypeInfo = new KrbETypeInfo2 { ETypeInfo = new[] { new KrbETypeInfo2Entry { EType = cred.EncryptionType, Salt = cred.Salt } } }; var infoPaData = new KrbPaData { Type = PaDataType.PA_ETYPE_INFO2, Value = etypeInfo.Encode().AsMemory() }; preAuthRequirements.Add(infoPaData); }
public static KrbPrincipalName FromPrincipal( IKerberosPrincipal principal, PrincipalNameType type = PrincipalNameType.NT_PRINCIPAL, string realm = null ) { return(FromString(principal.PrincipalName, type, realm)); }
protected virtual void EvaluateSecurityPolicy( IKerberosPrincipal principal, IKerberosPrincipal servicePrincipal ) { logger.LogDebug("Default policy evaluated for {User} to {Service}", principal.PrincipalName, servicePrincipal.PrincipalName); // good place to check whether the incoming principal is allowed to access the service principal }
public override void PostValidate(IKerberosPrincipal principal, List <KrbPaData> preAuthRequirements) { if (principal == null) { throw new ArgumentNullException(nameof(principal)); } if (preAuthRequirements == null) { throw new ArgumentNullException(nameof(preAuthRequirements)); } if (preAuthRequirements.Count <= 0) { // we don't want to include this if nothing is required otherwise we could // trigger a pre-auth check later in the flow or by the client in response return; } var entries = new List <KrbETypeInfo2Entry>(); foreach (EncryptionType type in Enum.GetValues(typeof(EncryptionType))) { if (!CryptoService.SupportsEType(type, this.Service.Configuration.Defaults.AllowWeakCrypto)) { continue; } var cred = principal.RetrieveLongTermCredential(type); if (cred != null) { entries.Add(new KrbETypeInfo2Entry { EType = cred.EncryptionType, Salt = cred.Salt }); } } var etypeInfo = new KrbETypeInfo2 { ETypeInfo = entries.ToArray() }; var infoPaData = new KrbPaData { Type = PaDataType.PA_ETYPE_INFO2, Value = etypeInfo.Encode() }; preAuthRequirements.Add(infoPaData); }
private static async Task <IEnumerable <KrbAuthorizationData> > GenerateAuthorizationData( IKerberosPrincipal principal, ServiceTicketRequest request ) { // authorization-data is annoying because it's a sequence of // ad-if-relevant, which is a sequence of sequences // it ends up looking something like // // [ // { // Type = ad-if-relevant, // Data = // [ // { // Type = pac, // Data = encoded-pac // }, // ... // ], // }, // ... // ] var authz = new List <KrbAuthorizationData>(); if (request.IncludePac) { var pac = await principal.GeneratePac(); if (pac != null) { var sequence = new KrbAuthorizationDataSequence { AuthorizationData = new[] { new KrbAuthorizationData { Type = AuthorizationDataType.AdWin2kPac, Data = pac.Encode(request.ServicePrincipalKey, request.ServicePrincipalKey) } } }; authz.Add(new KrbAuthorizationData { Type = AuthorizationDataType.AdIfRelevant, Data = sequence.Encode() }); } } return(authz); }
protected virtual Task EvaluateSecurityPolicy( IKerberosPrincipal principal, IKerberosPrincipal servicePrincipal, DecryptedKrbApReq apReqDecrypted ) { // good place to check whether the incoming principal is allowed to access the service principal // TODO: also maybe a good place to evaluate cross-realm requirements? return(Task.CompletedTask); }
private ReadOnlyMemory <byte> PreAuthFailed(KerberosValidationException kex, IKerberosPrincipal principal) { var err = new KrbError { ErrorCode = KerberosErrorCode.KDC_ERR_PREAUTH_FAILED, EText = kex.Message, Realm = RealmService.Name, SName = KrbPrincipalName.FromPrincipal(principal) }; return(err.EncodeApplication()); }
protected virtual Task EvaluateSecurityPolicy( IKerberosPrincipal principal, IKerberosPrincipal servicePrincipal ) { logger.LogDebug("Evaluating policy for {User} to {Service}", principal.PrincipalName, servicePrincipal.PrincipalName); // good place to check whether the incoming principal is allowed to access the service principal // TODO: also maybe a good place to evaluate cross-realm requirements? return(Task.CompletedTask); }
public IKerberosPrincipal Find(KrbPrincipalName principalName, string realm = null) { IKerberosPrincipal principal = null; if (principalName.FullyQualifiedName.EndsWith(this.realm, StringComparison.InvariantCultureIgnoreCase) || principalName.FullyQualifiedName.StartsWith("krbtgt", StringComparison.InvariantCultureIgnoreCase) || principalName.Type == PrincipalNameType.NT_PRINCIPAL) { principal = new FakeKerberosPrincipal(principalName.FullyQualifiedName); } return(principal); }
private static async Task <KrbAuthPack> ValidateAuthPack(IKerberosPrincipal principal, KrbPaPkAsReq pkreq) { SignedCms signedCms = new SignedCms(); signedCms.Decode(pkreq.SignedAuthPack.ToArray()); signedCms.CheckSignature(verifySignatureOnly: true); await principal.Validate(signedCms.Certificates); var authPack = KrbAuthPack.Decode(signedCms.ContentInfo.Content); return(authPack); }
private static async Task <IEnumerable <KrbAuthorizationData> > GenerateAuthorizationData( IKerberosPrincipal principal, KerberosKey krbtgt ) { // authorization-data is annoying because it's a sequence of // ad-if-relevant, which is a sequence of sequences // it ends up looking something like // // [ // { // Type = ad-if-relevant, // Data = // [ // { // Type = pac, // Data = encoded-pac // }, // ... // ], // }, // ... // ] var pac = await principal.GeneratePac(); var authz = new List <KrbAuthorizationData>(); var sequence = new KrbAuthorizationDataSequence { AuthorizationData = new[] { new KrbAuthorizationData { Type = AuthorizationDataType.AdWin2kPac, Data = pac.Encode(krbtgt, krbtgt) } } }; authz.Add(new KrbAuthorizationData { Type = AuthorizationDataType.AdIfRelevant, Data = sequence.Encode().AsMemory() }); return(authz); }
private async Task InvokePreAuthHandler( KrbKdcReq asReq, IKerberosPrincipal principal, List <KrbPaData> preAuthRequirements, PreAuthHandlerConstructor func ) { var handler = func(RealmService); var preAuthRequirement = await handler.Validate(asReq, principal); if (preAuthRequirement != null) { preAuthRequirements.Add(preAuthRequirement); } }
protected virtual IKerberosPrincipal TransformClientIdentity( DecryptedKrbApReq clientTicket, IKerberosPrincipal ticketIssuerIdentity ) { if (ticketIssuerIdentity.Type == PrincipalType.TrustedDomain) { // it's a referral from another realm so all the copy and filter // goop is in TransitedKerberosPrincipal.GeneratePac() return(new TransitedKerberosPrincipal(clientTicket)); } // 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(RealmService.Principals.Find(clientTicket.Ticket.CName)); }
protected virtual void EvaluateSecurityPolicy( IKerberosPrincipal principal, IKerberosPrincipal servicePrincipal ) { if (principal == null) { throw new ArgumentNullException(nameof(principal)); } if (servicePrincipal == null) { throw new ArgumentNullException(nameof(servicePrincipal)); } this.logger.LogDebug("Default policy evaluated for {User} to {Service}", principal.PrincipalName, servicePrincipal.PrincipalName); // good place to check whether the incoming principal is allowed to access the service principal }
private async Task <ReadOnlyMemory <byte> > GenerateAsRep(KrbAsReq asReq, IKerberosPrincipal principal) { // 1. detect if specific PAC contents are requested (claims) // 2. if requested generate PAC for user // 3. stuff PAC into ad-if-relevant pa-data of krbtgt ticket // 4. look up krbtgt account // 5. encrypt against krbtgt // 6. done var requirements = new List <KrbPaData>(); foreach (var handler in postProcessAuthHandlers) { await InvokePreAuthHandler(null, principal, requirements, handler.Value); } var rst = new ServiceTicketRequest { Principal = principal, Addresses = asReq.Body.Addresses, Nonce = asReq.Body.Nonce, IncludePac = true, Flags = TicketFlags.Initial | KrbKdcRep.DefaultFlags }; rst.EncryptedPartKey = await principal.RetrieveLongTermCredential(); var pacRequest = asReq.PaData.FirstOrDefault(pa => pa.Type == PaDataType.PA_PAC_REQUEST); if (pacRequest != null) { var paPacRequest = KrbPaPacRequest.Decode(pacRequest.Value); rst.IncludePac = paPacRequest.IncludePac; } var asRep = await KrbAsRep.GenerateTgt(rst, RealmService); asRep.PaData = requirements.ToArray(); return(asRep.EncodeApplication()); }
public void Setup() { var realmService = new FakeRealmService("CORP.BLAH.COM"); this.principal = realmService.Principals.Find(KrbPrincipalName.FromString("*****@*****.**")); this.pac = this.principal.GeneratePac(); this.key = new KerberosKey(new byte[32], etype: EncryptionType.AES256_CTS_HMAC_SHA1_96); var groups = new List <GroupMembership>(); for (var i = 0; i < this.GroupSize; i++) { groups.Add(new GroupMembership { Attributes = SidAttributes.SE_GROUP_ENABLED | SidAttributes.SE_GROUP_MANDATORY, RelativeId = (uint)i }); } this.pac.LogonInfo.GroupIds = groups; var extra = new List <RpcSidAttributes>(); for (var i = 0; i < this.ExtraSize; i++) { extra.Add(new RpcSidAttributes { Attributes = SidAttributes.SE_GROUP_ENABLED | SidAttributes.SE_GROUP_MANDATORY, Sid = new RpcSid() { IdentifierAuthority = new RpcSidIdentifierAuthority { IdentifierAuthority = new byte[] { 0, 0, 0, 0, 0, (byte)IdentifierAuthority.NTAuthority } }, SubAuthority = new uint[] { 21, 3333, 4444, 5555, 111 }, Revision = 1 } }); } this.pac.LogonInfo.ExtraIds = extra; }
private async Task <ReadOnlyMemory <byte> > GenerateAsRep(KrbKdcReq asReq, IKerberosPrincipal principal) { // 1. detect if specific PAC contents are requested (claims) // 2. if requested generate PAC for user // 3. stuff PAC into ad-if-relevant pa-data of krbtgt ticket // 4. look up krbtgt account // 5. encrypt against krbtgt // 6. done var requirements = new List <KrbPaData>(); foreach (var handler in postProcessAuthHandlers) { await InvokePreAuthHandler(null, principal, requirements, handler.Value); } var asRep = await KrbAsRep.GenerateTgt(principal, requirements, RealmService, asReq.Body); return(asRep.EncodeApplication()); }
public static async Task <KrbAsRep> GenerateTgt( IKerberosPrincipal principal, IEnumerable <KrbPaData> requirements, IRealmService realmService, KrbKdcReqBody asReq ) { // This is approximately correct such that a client doesn't barf on it // The krbtgt Ticket structure is probably correct as far as AD thinks // Modulo the PAC, at least. var longTermKey = await principal.RetrieveLongTermCredential(); var servicePrincipal = await realmService.Principals.RetrieveKrbtgt(); var servicePrincipalKey = await servicePrincipal.RetrieveLongTermCredential(); var now = realmService.Now(); KrbAsRep asRep = await GenerateServiceTicket <KrbAsRep>( new ServiceTicketRequest { Principal = principal, EncryptedPartKey = longTermKey, ServicePrincipal = servicePrincipal, ServicePrincipalKey = servicePrincipalKey, Now = now, Addresses = asReq.Addresses, RenewTill = now + realmService.Settings.MaximumRenewalWindow, StartTime = now - realmService.Settings.MaximumSkew, EndTime = now + realmService.Settings.SessionLifetime, Flags = DefaultFlags, RealmName = realmService.Name, Nonce = asReq.Nonce } ); asRep.PaData = requirements.ToArray(); return(asRep); }
public static KrbPrincipalName FromPrincipal( IKerberosPrincipal principal, PrincipalNameType type = PrincipalNameType.NT_PRINCIPAL, string realm = null ) { var nameSplit = principal.PrincipalName.Split('@'); var name = nameSplit[0]; if (string.IsNullOrWhiteSpace(realm) && nameSplit.Length > 0) { realm = nameSplit[1]; } return(new KrbPrincipalName { Type = type, Name = new[] { name, realm.ToUpperInvariant() } }); }
public IKerberosPrincipal Find(KrbPrincipalName principalName, string realm = null) { IKerberosPrincipal principal = null; bool fallback = false; if (principalName.FullyQualifiedName.Contains("-fallback", StringComparison.OrdinalIgnoreCase) && principalName.Type == PrincipalNameType.NT_ENTERPRISE) { principal = null; fallback = true; } if ((principalName.FullyQualifiedName.EndsWith(this.realm, StringComparison.InvariantCultureIgnoreCase) || principalName.FullyQualifiedName.StartsWith("krbtgt", StringComparison.InvariantCultureIgnoreCase) || principalName.Type == PrincipalNameType.NT_PRINCIPAL) && !fallback) { principal = new FakeKerberosPrincipal(principalName.FullyQualifiedName); } return(principal); }
public override Task PostValidate(IKerberosPrincipal principal, List <KrbPaData> preAuthRequirements) { return(base.PostValidate(principal, preAuthRequirements)); }
public virtual Task PostValidate(IKerberosPrincipal principal, List <KrbPaData> preAuthRequirements) { return(Task.CompletedTask); }
public virtual Task <KrbPaData> Validate(KrbKdcReq asReq, IKerberosPrincipal principal) { return(Task.FromResult <KrbPaData>(null)); }
/// <summary> /// Execute the PA-Data validation phase and verify if the presented message meets the requirement of the handler. /// </summary> /// <param name="asReq">The authentication request message</param> /// <param name="principal">The user principal found during the AS-REQ processing that should be evaluated by this handler</param> /// <returns>Optionally returns PA-Data that should be returned to the client in the response</returns> public virtual KrbPaData Validate(KrbKdcReq asReq, IKerberosPrincipal principal) => null;
/// <summary> /// Executes after the pre-auth validation has completed and can be used to modify the response message /// </summary> /// <param name="principal">The authenticated principal</param> /// <param name="preAuthRequirements">The list of PA-Data that will be sent in the response message</param> public virtual void PostValidate(IKerberosPrincipal principal, List <KrbPaData> preAuthRequirements) { }
public void Add(string name, IKerberosPrincipal principal) { _principals.Add(name, principal); }
private ReadOnlyMemory <byte> RequirePreAuth(IEnumerable <KrbPaData> preAuthRequests, IKerberosPrincipal principal) { logger.LogTrace("AS-REQ requires pre-auth for user {User}", principal.PrincipalName); var err = new KrbError { ErrorCode = KerberosErrorCode.KDC_ERR_PREAUTH_REQUIRED, EText = "", Realm = RealmService.Name, SName = KrbPrincipalName.FromPrincipal(principal), EData = new KrbMethodData { MethodData = preAuthRequests.ToArray() }.Encode() }; return(err.EncodeApplication()); }
private async Task <IEnumerable <KrbPaData> > ProcessPreAuth(KrbKdcReq asReq, IKerberosPrincipal principal) { // 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. var invokingAuthTypes = PreAuthHandlers.Keys.Intersect(principal.SupportedPreAuthenticationTypes); var preAuthRequirements = new List <KrbPaData>(); foreach (var preAuthType in invokingAuthTypes) { await InvokePreAuthHandler(asReq, principal, 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(principal, preAuthRequirements); } return(preAuthRequirements); }