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);
        }
예제 #2
0
        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);
        }
예제 #3
0
 public static KrbPrincipalName FromPrincipal(
     IKerberosPrincipal principal,
     PrincipalNameType type = PrincipalNameType.NT_PRINCIPAL,
     string realm           = null
     )
 {
     return(FromString(principal.PrincipalName, type, realm));
 }
예제 #4
0
        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);
        }
예제 #6
0
        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);
        }
예제 #7
0
        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());
        }
예제 #9
0
        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);
        }
예제 #10
0
        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);
        }
예제 #12
0
        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);
            }
        }
예제 #14
0
        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));
        }
예제 #15
0
        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());
        }
예제 #17
0
        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;
        }
예제 #18
0
        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());
        }
예제 #19
0
        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);
        }
예제 #20
0
        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() }
            });
        }
예제 #21
0
        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)
 {
 }
예제 #27
0
 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);
        }