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