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, EncryptedPartEType = context.EncryptedPartEType, 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 = GetPreferredEType( asReq.Body.EType, this.RealmService.Configuration.Defaults.PermittedEncryptionTypes, this.RealmService.Configuration.Defaults.AllowWeakCrypto ), Compatibility = this.RealmService.Settings.Compatibility, }; 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()); }
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, EncryptedPartEType = context.EncryptedPartEType, 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 = GetPreferredEType( tgsReq.Body.EType, this.RealmService.Configuration.Defaults.PermittedEncryptionTypes, this.RealmService.Configuration.Defaults.AllowWeakCrypto ), Compatibility = this.RealmService.Settings.Compatibility, }; if (tgsReq.Body.KdcOptions.HasFlag(KdcOptions.Canonicalize)) { rst.SamAccountName = context.GetState <TgsState>(PaDataType.PA_TGS_REQ).DecryptedApReq.Ticket.CName.FullyQualifiedName; } // 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()); }