public virtual void TransformKdcReq(KrbKdcReq req) { if (req == null) { throw new ArgumentNullException(nameof(req)); } var ts = KrbPaEncTsEnc.Now(); var tsEncoded = ts.Encode(); var padata = req.PaData.ToList(); var key = this.CreateKey(); KrbEncryptedData encData = KrbEncryptedData.Encrypt( tsEncoded, key, KeyUsage.PaEncTs ); padata.Add(new KrbPaData { Type = PaDataType.PA_ENC_TIMESTAMP, Value = encData.Encode() }); req.PaData = padata.ToArray(); }
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); }
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); }
public override async Task <KrbPaData> Validate(KrbKdcReq asReq, PreAuthenticationContext preauth) { if (preauth.PreAuthenticationSatisfied) { return(null); } var principal = preauth.Principal; var cred = await principal.RetrieveLongTermCredential(); var timestamp = asReq.DecryptTimestamp(cred); if (timestamp == DateTimeOffset.MinValue) { return(new KrbPaData { Type = PaDataType.PA_ENC_TIMESTAMP }); } var skew = Service.Settings.MaximumSkew; DateTimeOffset now = Service.Now(); if (Abs(now - timestamp) > skew) { throw new KerberosValidationException( $"Timestamp window is greater than allowed skew. Start: {timestamp}; End: {now}; Skew: {skew}" ); } preauth.EncryptedPartKey = cred; return(null); }
/// <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 static bool?DetectPacRequirement(KrbKdcReq asReq) { var pacRequest = asReq.PaData.FirstOrDefault(pa => pa.Type == PaDataType.PA_PAC_REQUEST); if (pacRequest != null) { var paPacRequest = KrbPaPacRequest.Decode(pacRequest.Value); return(paPacRequest.IncludePac); } return(null); }
private async Task InvokePreAuthHandler( KrbKdcReq asReq, PreAuthenticationContext preauth, List <KrbPaData> preAuthRequirements, PreAuthHandlerConstructor func ) { var handler = func(RealmService); var preAuthRequirement = await handler.Validate(asReq, preauth); if (preAuthRequirement != null) { preAuthRequirements.Add(preAuthRequirement); } }
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); } }
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.PreAuthenticationSatisfied) { return(null); } var principal = preauth.Principal; var cred = principal.RetrieveLongTermCredential(); var timestamp = asReq.DecryptTimestamp(cred, out EncryptionType etype); if (timestamp == DateTimeOffset.MinValue) { return(new KrbPaData { Type = PaDataType.PA_ENC_TIMESTAMP }); } var skew = this.Service.Settings.MaximumSkew; DateTimeOffset now = this.Service.Now(); if (Abs(now - timestamp) > skew) { throw new KerberosValidationException( $"Timestamp window is greater than allowed skew. Start: {timestamp}; End: {now}; Skew: {skew}" ); } preauth.EncryptedPartKey = cred; preauth.EncryptedPartEType = etype; preauth.ClientAuthority = PaDataType.PA_ENC_TIMESTAMP; return(null); }
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 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()); }
public virtual void TransformKdcReq(KrbKdcReq req) { var ts = KrbPaEncTsEnc.Now(); var tsEncoded = ts.Encode(); var padata = req.PaData.ToList(); var key = CreateKey(); KrbEncryptedData encData = KrbEncryptedData.Encrypt( tsEncoded, key, KeyUsage.PaEncTs ); padata.Add(new KrbPaData { Type = PaDataType.PA_ENC_TIMESTAMP, Value = encData.Encode() }); req.PaData = padata.ToArray(); }
/// <summary> /// Executes the primary validation process for the pre-auth data. /// </summary> /// <param name="asReq">The message to validate</param> /// <param name="context">The current context of the request</param> /// <returns>Optionally returns PA-Data that needs to be sent to the client otherwise returns null.</returns> public override KrbPaData Validate(KrbKdcReq asReq, PreAuthenticationContext context) { // First we authenticate the incoming request // // 1. Get the ApReq (TGT) from the PA-Data of the request // 2. Decrypt the TGT and extract the client calling identity if (context.EvidenceTicketIdentity == null) { // we wouldn't ever hit this in the normal case, but we could hit it // if a referral came in from a realm we don't recognize or don't trust throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_S_PRINCIPAL_UNKNOWN); } var evidenceSName = KrbPrincipalName.FromPrincipal(context.EvidenceTicketIdentity, PrincipalNameType.NT_SRV_INST); if (!evidenceSName.IsKrbtgt()) { // spec-wise this isn't exactly correct as the authz ticket might be for renewal // we will deviate from the spec because that's how other KDCs operate today // KDC_ERR_PADATA_TYPE_NOSUPP is the closest error to indicate the way you // authenticated the request is not something we're willing to accept throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_PADATA_TYPE_NOSUPP); } // we need to validate that the evidence ticket is the KDC service (krbtgt) // it might either be our KDC that issued it, or a KDC from another realm (referral) // if it's ours it'll match our service name (krbtgt), and it'll decrypt with our key // if it's a referral it'll match a trusted realm's name and decrypt with their key // if it's a referral then the incoming identity will also need to be transposed // no matter what we only ever want the TGS service ticket // it might belong to another realm, but that's ok because it could be a referral // it is a krbtgt service identity we recognize // it could be ours, or a referral from a trusted realm // in either case we can and will validate the ticket and // extract the user principal from within the krbtgt ticket var krbtgtKey = context.EvidenceTicketIdentity.RetrieveLongTermCredential(); if (krbtgtKey == null) { // since the key comes from caller-implemented code we // should check to make sure they gave us a usable key throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_ETYPE_NOSUPP); } if (context.EvidenceTicketKey == null) { context.EvidenceTicketKey = krbtgtKey; } var state = context.GetState <TgsState>(PaDataType.PA_TGS_REQ); if (state.DecryptedApReq == null) { state.DecryptedApReq = DecryptApReq(state.ApReq, context.EvidenceTicketKey); } context.EncryptedPartKey = state.DecryptedApReq.SessionKey; context.Ticket = state.DecryptedApReq.Ticket; return(null); }
public override async Task <KrbPaData> Validate(KrbKdcReq asReq, PreAuthenticationContext preauth) { var paPk = asReq.PaData.FirstOrDefault(p => p.Type == PaDataType.PA_PK_AS_REQ); if (paPk == null) { return(null); } var pkreq = KrbPaPkAsReq.Decode(paPk.Value); var authPack = await ValidateAuthPack(preauth.Principal, pkreq); ValidateAuthenticator(authPack.PKAuthenticator, asReq.Body); var requestAlg = authPack.ClientPublicValue?.Algorithm?.Algorithm; IKeyAgreement agreement; if (requestAlg?.Value == EllipticCurveDiffieHellman.Value) { agreement = FromEllipticCurveDomainParameters(authPack.ClientPublicValue); } else if (requestAlg?.Value == DiffieHellman.Value) { agreement = await FromDiffieHellmanDomainParametersAsync(authPack.ClientPublicValue); } else { throw OnlyKeyAgreementSupportedException(); } var derivedKey = agreement.GenerateAgreement(); var etype = asReq.Body.EType.First(); 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); await 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 = await SignDHResponseAsync(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); return(null); }
public void GenerateAsReqApplicationMessage() { var ts = new KrbPaEncTsEnc { PaTimestamp = new DateTimeOffset(636973454050000000, TimeSpan.Zero), PaUSec = 868835 }; var key = CreateKey(); var tsEncoded = ts.Encode(); KrbEncryptedData encData = KrbEncryptedData.Encrypt( tsEncoded, key, KeyUsage.PaEncTs ); Assert.IsTrue(tsEncoded.Span.SequenceEqual(encData.Decrypt(key, KeyUsage.PaEncTs, d => KrbPaEncTsEnc.Decode(d)).Encode().Span)); var asreq = new KrbAsReq() { MessageType = MessageType.KRB_AP_REQ, ProtocolVersionNumber = 5, Body = new KrbKdcReqBody { Addresses = new[] { new KrbHostAddress { AddressType = AddressType.NetBios, Address = Encoding.ASCII.GetBytes("APP03 ") } }, CName = new KrbPrincipalName { Name = new[] { "*****@*****.**" }, Type = PrincipalNameType.NT_ENTERPRISE }, EType = new[] { EncryptionType.AES256_CTS_HMAC_SHA1_96, EncryptionType.AES128_CTS_HMAC_SHA1_96, EncryptionType.RC4_HMAC_NT, EncryptionType.RC4_HMAC_NT_EXP, EncryptionType.RC4_HMAC_OLD_EXP, EncryptionType.DES_CBC_MD5 }, KdcOptions = KdcOptions.RenewableOk | KdcOptions.Canonicalize | KdcOptions.Renewable | KdcOptions.Forwardable, Nonce = 717695934, RTime = new DateTimeOffset(642720196850000000L, TimeSpan.Zero), Realm = "CORP.IDENTITYINTERVENTION.COM", SName = new KrbPrincipalName { Type = PrincipalNameType.NT_SRV_INST, Name = new[] { "krbtgt", "CORP.IDENTITYINTERVENTION.COM" } }, Till = new DateTimeOffset(642720196850000000L, TimeSpan.Zero) }, PaData = new[] { new KrbPaData { Type = PaDataType.PA_ENC_TIMESTAMP, Value = new ReadOnlyMemory <byte>(encData.Encode().ToArray()) }, new KrbPaData { Type = PaDataType.PA_PAC_REQUEST, Value = new ReadOnlyMemory <byte>(new KrbPaPacRequest { IncludePac = true }.Encode().ToArray()) } } }; var encodedAsReq = asreq.Encode().ToArray(); var roundtrip = KrbKdcReq.Decode(encodedAsReq); Assert.IsNotNull(roundtrip); }
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 = GetPreferredEType( asReq.Body.EType, this.Service.Configuration.Defaults.PermittedEncryptionTypes, this.Service.Configuration.Defaults.AllowWeakCrypto ); 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 virtual Task <KrbPaData> Validate(KrbKdcReq asReq, IKerberosPrincipal principal) { return(Task.FromResult <KrbPaData>(null)); }
/// <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) { if (req == null) { throw new ArgumentNullException(nameof(req)); } this.agreement = this.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 (this.agreement == null) { throw OnlyKeyAgreementSupportedException(); } var padata = req.PaData.ToList(); KrbAuthPack authPack; if (this.SupportsEllipticCurveDiffieHellman) { authPack = this.CreateEllipticCurveDiffieHellmanAuthPack(req.Body); } else if (this.SupportsDiffieHellman) { authPack = this.CreateDiffieHellmanAuthPack(req.Body); } else { throw OnlyKeyAgreementSupportedException(); } Now(out DateTimeOffset ctime, out int usec); authPack.PKAuthenticator.CTime = ctime; authPack.PKAuthenticator.CuSec = usec; SignedCms signed = new SignedCms( new ContentInfo( IdPkInitAuthData, authPack.Encode().ToArray() ) ); var signer = new CmsSigner(this.Certificate) { IncludeOption = this.IncludeOption }; signed.ComputeSignature(signer, silent: !CanPrompt); var pk = new KrbPaPkAsReq { SignedAuthPack = signed.Encode() }; padata.Add(new KrbPaData { Type = PaDataType.PA_PK_AS_REQ, Value = pk.Encode() }); req.PaData = padata.ToArray(); }
/// <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="preauth">Contains the current state of the request to inform the outer message handler</param> /// <returns>Optionally returns PA-Data that should be returned to the client in the response</returns> public virtual KrbPaData Validate(KrbKdcReq asReq, PreAuthenticationContext preauth) { return(Validate(asReq, preauth.Principal)); }
/// <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;
private static KrbApReq ExtractApReq(KrbKdcReq tgsReq) { var paData = tgsReq.PaData.First(p => p.Type == PaDataType.PA_TGS_REQ); return(paData.DecodeApReq()); }