示例#1
0
        public override void Validate(ValidationActions validation)
        {
            var now = Now();

            var ctime = Response.CTime.AddTicks(Response.CuSec / 10);

            if (validation.HasFlag(ValidationActions.TokenWindow))
            {
                ValidateTicketSkew(now, Skew, ctime);
            }

            if (KerberosConstants.TimeEquals(CTime, Response.CTime))
            {
                throw new KerberosValidationException(
                          $"CTime does not match. Sent: {CTime.Ticks}; Received: {Response.CTime.Ticks}"
                          );
            }

            if (CuSec != Response.CuSec)
            {
                throw new KerberosValidationException(
                          $"CuSec does not match. Sent: {CuSec}; Received: {Response.CuSec}"
                          );
            }

            if (SequenceNumber != Response.SequenceNumber)
            {
                throw new KerberosValidationException(
                          $"SequenceNumber does not match. Sent: {SequenceNumber}; Received: {Response.SequenceNumber}"
                          );
            }
        }
示例#2
0
        internal static KrbPaEncTsEnc Now()
        {
            var ts = new KrbPaEncTsEnc();

            KerberosConstants.Now(out ts.PaTimestamp, out ts.PaUSec);

            return(ts);
        }
        /// <summary>
        /// Applies credential-specific changes to the KDC-REQ message and is what supplies the PKINIT properties to the request.
        /// </summary>
        /// <param name="req">The <see cref="KrbKdcReq"/> that will be modified.</param>
        public override void TransformKdcReq(KrbKdcReq req)
        {
            agreement = StartKeyAgreement();

            // We don't support the straight RSA mode because
            // it doesn't rely on ephemeral key agreement
            // which isn't great security-wise

            if (agreement == null)
            {
                throw OnlyKeyAgreementSupportedException();
            }

            var padata = req.PaData.ToList();

            KrbAuthPack authPack;

            if (SupportsEllipticCurveDiffieHellman)
            {
                authPack = CreateEllipticCurveDiffieHellmanAuthPack(req.Body);
            }
            else if (SupportsDiffieHellman)
            {
                authPack = CreateDiffieHellmanAuthPack(req.Body);
            }
            else
            {
                throw OnlyKeyAgreementSupportedException();
            }

            KerberosConstants.Now(out authPack.PKAuthenticator.CTime, out authPack.PKAuthenticator.CuSec);

            SignedCms signed = new SignedCms(
                new ContentInfo(
                    IdPkInitAuthData,
                    authPack.Encode().ToArray()
                    )
                );

            var signer = new CmsSigner(Certificate)
            {
                IncludeOption = IncludeOption
            };

            signed.ComputeSignature(signer, silent: true);

            var pk = new KrbPaPkAsReq {
                SignedAuthPack = signed.Encode()
            };

            padata.Add(new KrbPaData
            {
                Type  = PaDataType.PA_PK_AS_REQ,
                Value = pk.Encode()
            });

            req.PaData = padata.ToArray();
        }
示例#4
0
 protected virtual void ValidateTicketSkew(DateTimeOffset now, TimeSpan skew, DateTimeOffset ctime)
 {
     if (!KerberosConstants.WithinSkew(now, ctime, 0, skew))
     {
         throw new KerberosValidationException(
                   $"Token window is greater than allowed skew. Start: {ctime}; End: {now}; Allowed Skew: {skew}",
                   nameof(skew)
                   );
     }
 }
示例#5
0
        private ReadOnlyMemory <byte> String2Key(byte[] password, string salt, byte[] param)
        {
            var passwordBytes = KerberosConstants.UnicodeBytesToUtf8(password);

            var iterations = GetIterations(param, 4096);

            var saltBytes = KerberosConstants.UnicodeStringToUtf8(salt);

            var random = PBKDF2(passwordBytes, saltBytes, iterations, KeySize);

            return(DK(random, KerberosConstant, KeySize, BlockSize));
        }
示例#6
0
        private ReadOnlySpan <byte> String2Key(byte[] password, string salt, byte[] param)
        {
            var passwordBytes = KerberosConstants.UnicodeBytesToUtf8(password);

            var iterations = GetIterations(param, 4096);

            var saltBytes = GetSaltBytes(salt, null);

            var random = PBKDF2(passwordBytes, saltBytes, iterations, KeySize);

            var tmpKey = Random2Key(random);

            return(DK(tmpKey, KerberosConstant.Span, KeySize, BlockSize));
        }
示例#7
0
        public override void TransformKdcReq(KrbKdcReq req)
        {
            var padata = req.PaData.ToList();

            KrbAuthPack authPack;

            if (SupportsEllipticCurveDiffieHellman)
            {
                authPack = CreateEllipticCurveDiffieHellmanAuthPack(req.Body);
            }
            else if (SupportsDiffieHellman)
            {
                authPack = CreateDiffieHellmanAuthPack(req.Body);
            }
            else
            {
                throw OnlyKeyAgreementSupportedException();
            }

            KerberosConstants.Now(out authPack.PKAuthenticator.CTime, out authPack.PKAuthenticator.CuSec);

            SignedCms signed = new SignedCms(
                new ContentInfo(
                    IdPkInitAuthData,
                    authPack.Encode().ToArray()
                    )
                );

            var signer = new CmsSigner(Certificate)
            {
                IncludeOption = IncludeOption
            };

            signed.ComputeSignature(signer, silent: true);

            var pk = new KrbPaPkAsReq {
                SignedAuthPack = signed.Encode()
            };

            padata.Add(new KrbPaData
            {
                Type  = PaDataType.PA_PK_AS_REQ,
                Value = pk.Encode()
            });

            req.PaData = padata.ToArray();
        }
        private KrbAuthPack CreateDiffieHellmanAuthPack(KrbKdcReqBody body)
        {
            using (var sha1 = CryptoPal.Platform.Sha1())
            {
                var encoded = body.Encode();

                var paChecksum = sha1.ComputeHash(encoded.Span);

                var parametersAreCached = this.CacheKeyAgreementParameters(this.agreement);

                if (parametersAreCached)
                {
                    var etype = KerberosConstants.GetPreferredEType(body.EType);

                    if (etype is null)
                    {
                        throw new InvalidOperationException("Cannot find a common EType");
                    }

                    this.clientDHNonce = GenerateNonce(etype.Value, this.agreement.PublicKey.KeyLength);
                }

                var domainParams = KrbDiffieHellmanDomainParameters.FromKeyAgreement(this.agreement);

                var authPack = new KrbAuthPack
                {
                    PKAuthenticator = new KrbPKAuthenticator
                    {
                        Nonce      = body.Nonce,
                        PaChecksum = paChecksum
                    },
                    ClientPublicValue = new KrbSubjectPublicKeyInfo
                    {
                        Algorithm = new KrbAlgorithmIdentifier
                        {
                            Algorithm  = DiffieHellman,
                            Parameters = domainParams.EncodeSpecial()
                        },
                        SubjectPublicKey = this.agreement.PublicKey.EncodePublicKey()
                    },
                    ClientDHNonce = this.clientDHNonce
                };

                return(authPack);
            }
        }
示例#9
0
        private static ReadOnlySpan <byte> GetSaltBytes(string salt, string pepper)
        {
            var saltBytes = KerberosConstants.UnicodeStringToUtf8(salt);

            if (string.IsNullOrWhiteSpace(pepper))
            {
                return(saltBytes);
            }

            var pepperBytes = KerberosConstants.UnicodeStringToUtf8(pepper);

            var results = new Span <byte>(new byte[saltBytes.Length + 1 + pepperBytes.Length]);

            pepperBytes.CopyTo(results);

            saltBytes.CopyTo(results.Slice(pepperBytes.Length + 1, saltBytes.Length));

            return(results);
        }
        private void ValidateAuthenticator(KrbPKAuthenticator authenticator, KrbKdcReqBody body)
        {
            using (var sha1 = CryptoPal.Platform.Sha1())
            {
                var encoded = body.Encode();

                var paChecksum = sha1.ComputeHash(encoded.Span);

                if (!KerberosCryptoTransformer.AreEqualSlow(paChecksum.Span, authenticator.PaChecksum.Value.Span))
                {
                    throw new SecurityException("Invalid checksum");
                }
            }

            if (!KerberosConstants.WithinSkew(Service.Now(), authenticator.CTime, authenticator.CuSec, Service.Settings.MaximumSkew))
            {
                throw new KerberosValidationException($"PKAuthenticator time skew too great");
            }

            ValidateNonce(authenticator.Nonce);
        }
示例#11
0
        private ReadOnlyMemory <byte> GenerateAsRep(KrbAsReq asReq, PreAuthenticationContext context)
        {
            // 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 rst = new ServiceTicketRequest
            {
                Principal        = context.Principal,
                EncryptedPartKey = context.EncryptedPartKey,
                ServicePrincipal = context.ServicePrincipal,
                Addresses        = asReq.Body.Addresses,
                Nonce            = asReq.Body.Nonce,
                Now                   = this.RealmService.Now(),
                StartTime             = asReq.Body.From ?? DateTimeOffset.MinValue,
                EndTime               = asReq.Body.Till,
                MaximumTicketLifetime = this.RealmService.Settings.SessionLifetime,
                Flags                 = TicketFlags.Initial | KrbKdcRep.DefaultFlags,
                PreferredClientEType  = KerberosConstants.GetPreferredEType(asReq.Body.EType),
            };

            if (context.ClientAuthority != PaDataType.PA_NONE)
            {
                rst.Flags |= TicketFlags.PreAuthenticated;
            }

            // Canonicalize means the CName in the reply is allowed to be different from the CName in the request.
            // If this is not allowed, then we must use the CName from the request. Otherwise, we will set the CName
            // to what we have in our realm, i.e. user@realm.
            if (!asReq.Body.KdcOptions.HasFlag(KdcOptions.Canonicalize))
            {
                rst.SamAccountName = asReq.Body.CName.FullyQualifiedName;
            }

            if (rst.EncryptedPartKey == null)
            {
                rst.EncryptedPartKey = rst.Principal.RetrieveLongTermCredential();
            }

            if (context.IncludePac == null)
            {
                context.IncludePac = DetectPacRequirement(asReq);
            }

            rst.IncludePac = context.IncludePac ?? false;

            // 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 asRep = KrbAsRep.GenerateTgt(rst, this.RealmService);

            if (context.PaData != null)
            {
                asRep.PaData = context.PaData.ToArray();
            }

            return(asRep.EncodeApplication());
        }
示例#12
0
        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.PreAuthenticationState.TryGetValue(PaDataType.PA_PK_AS_REQ, out PaDataState paState) ||
                !(paState is PkInitState state))
            {
                return(null);
            }

            var authPack = ValidateAuthPack(preauth, state);

            this.ValidateAuthenticator(authPack.PKAuthenticator, asReq.Body);

            var requestAlg = authPack.ClientPublicValue?.Algorithm?.Algorithm;

            IKeyAgreement agreement;

            if (requestAlg?.Value == EllipticCurveDiffieHellman.Value)
            {
                agreement = this.FromEllipticCurveDomainParameters(authPack.ClientPublicValue);
            }
            else if (requestAlg?.Value == DiffieHellman.Value)
            {
                agreement = this.FromDiffieHellmanDomainParameters(authPack.ClientPublicValue);
            }
            else
            {
                throw OnlyKeyAgreementSupportedException();
            }

            var derivedKey = agreement.GenerateAgreement();

            var preferredEType = KerberosConstants.GetPreferredEType(asReq.Body.EType);

            if (preferredEType is null)
            {
                throw new InvalidOperationException("Cannot find a common EType");
            }

            var etype = preferredEType.Value;

            var transform = CryptoService.CreateTransform(etype);

            ReadOnlyMemory <byte> clientDHNonce = authPack.ClientDHNonce.GetValueOrDefault();
            ReadOnlyMemory <byte> serverDHNonce = default;

            if (clientDHNonce.Length > 0)
            {
                serverDHNonce = transform.GenerateRandomBytes(agreement.PublicKey.KeyLength);

                this.Service.Principals.CacheKey(agreement.PrivateKey);
            }

            var keyInfo = new KrbKdcDHKeyInfo {
                SubjectPublicKey = agreement.PublicKey.EncodePublicKey()
            };

            if (agreement.PublicKey.CacheExpiry.HasValue)
            {
                keyInfo.DHKeyExpiration = agreement.PublicKey.CacheExpiry;
                keyInfo.Nonce           = authPack.PKAuthenticator.Nonce;
            }

            var sessionKey = PKInitString2Key.String2Key(
                derivedKey.Span,
                transform.KeySize,
                clientDHNonce.Span,
                serverDHNonce.Span
                );

            var paPkRep = new KrbPaPkAsRep
            {
                DHInfo = new KrbDHReplyInfo
                {
                    DHSignedData  = this.SignDHResponse(keyInfo),
                    ServerDHNonce = serverDHNonce
                }
            };

            preauth.PaData = new[]
            {
                new KrbPaData
                {
                    Type  = PaDataType.PA_PK_AS_REP,
                    Value = paPkRep.Encode()
                }
            };

            preauth.EncryptedPartKey = new KerberosKey(key: sessionKey.ToArray(), etype: etype);
            preauth.ClientAuthority  = PaDataType.PA_PK_AS_REQ;

            return(null);
        }
示例#13
0
        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,
                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 = KerberosConstants.GetPreferredEType(tgsReq.Body.EType),
            };

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