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);
            }
        }
Beispiel #2
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());
        }
Beispiel #3
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);
        }
Beispiel #4
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());
        }