gpt4 book ai didi

java - Samba 4.11 上使用多个 SPN 和 AES256 的 Kerberos SPNEGO 身份验证

转载 作者:行者123 更新时间:2023-12-01 16:36:07 26 4
gpt4 key购买 nike

我有一个用 Kotlin 编写的 HTTP 服务,并使用 Tomcat 在多个域中监听,并且这些服务需要通过 Kerberos 进行身份验证。在 Samba 4.9 上,我们有一个用户拥有多个启用了 AES256 加密的 SPN。为该用户生成了包含所有 SPN 的 key 表。

升级到 Samba 4.11 后,单个用户中的多个 SPN 停止工作。抛出错误获取初始凭据时在 Kerberos 数据库中找不到客户端“HTTP/a.example.com@CORP.EXAMPLE.COM”。我们通过创建多个用户(每个 SPN 一个用户)并将 UPN 设置为单个 SPN 的值来修复此问题。之后,我们为每个用户生成 key 表,然后将其合并。

问题是,当我收到带有 aes256-cts-hmac-sha1-96 的票证时,会抛出 java.security.GeneralSecurityException: Checksum failed 并且正常工作在一个域中,我用作主体的域。 arcfour-hmac-md5 在所有域上都可以正常工作,但我需要支持 AES 加密。

我已经在旧的 Samba 4.9 上测试了这个场景,并且发生了同样的情况。如果我们有多个用户,每个用户都有一个 SPN,并且有一个包含所有用户的 key 表,也会抛出 Checksum failed

因此,要么我设法让具有多个 SPN 的单个用户在 Samba 4.11 上工作,要么在使用 AES 加密时必须摆脱校验和失败

java-版本

openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment 18.9 (build 11.0.6+10)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.6+10, mixed mode)

JAVA_OPTS

-Dsun.security.krb5.disableReferrals=true
-Dsun.security.krb5.debug=true
-Dsun.security.spnego.debug=true

.java.login.config

example {
com.sun.security.auth.module.Krb5LoginModule required
keyTab="/root/HTTP.keytab"
principal="HTTP/a.example.com@CORP.EXAMPLE.COM"
debug=true
storeKey=true
useKeyTab=true;
};

HTTP.keytab

Vno  Type                     Principal                          
2 aes256-cts-hmac-sha1-96 HTTP/a.example.com@CORP.EXAMPLE.COM
2 aes128-cts-hmac-sha1-96 HTTP/a.example.com@CORP.EXAMPLE.COM
2 arcfour-hmac-md5 HTTP/a.example.com@CORP.EXAMPLE.COM
2 des-cbc-md5-deprecated HTTP/a.example.com@CORP.EXAMPLE.COM
2 des-cbc-crc-deprecated HTTP/a.example.com@CORP.EXAMPLE.COM
2 aes256-cts-hmac-sha1-96 HTTP/b.example.com@CORP.EXAMPLE.COM
2 aes128-cts-hmac-sha1-96 HTTP/b.example.com@CORP.EXAMPLE.COM
2 arcfour-hmac-md5 HTTP/b.example.com@CORP.EXAMPLE.COM
2 des-cbc-md5-deprecated HTTP/b.example.com@CORP.EXAMPLE.COM
2 des-cbc-crc-deprecated HTTP/b.example.com@CORP.EXAMPLE.COM

HealthServlet.kt

import org.ietf.jgss.GSSCredential
import org.ietf.jgss.GSSManager
import org.ietf.jgss.Oid
import java.io.IOException
import java.security.PrivilegedActionException
import java.security.PrivilegedExceptionAction
import java.util.Base64
import javax.security.auth.Subject
import javax.security.auth.login.LoginContext
import javax.security.auth.login.LoginException
import javax.servlet.ServletException
import javax.servlet.annotation.WebServlet
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

@WebServlet("/healthz")
class HealthServlet : HttpServlet() {
@Throws(ServletException::class, IOException::class)
override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
val authorization = req.getHeader("Authorization") ?: let {
resp.addHeader("WWW-Authenticate", "Negotiate")
resp.status = HttpServletResponse.SC_UNAUTHORIZED
return
}

val negotiate = authorization.substringAfter(' ')
val token = Base64.getDecoder().decode(negotiate)

// Get own Kerberos credentials for accepting connection
val manager = GSSManager.getInstance()
val spnegoOid = Oid("1.3.6.1.5.5.2")

var serverCreds: GSSCredential? = null
this.loginAndAction(PrivilegedExceptionAction {
serverCreds = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, spnegoOid, GSSCredential.ACCEPT_ONLY)
})

val context = manager.createContext(serverCreds as GSSCredential)

val respToken = context!!.acceptSecContext(token, 0, token.size)
val respNegotiate = Base64.getEncoder().encodeToString(respToken)

// Send a token to the peer if one was generated by
// acceptSecContext
if (respToken != null) {
System.err.println("Will send token of size " + token.size + " from acceptSecContext.")

resp.addHeader("WWW-Authenticate", "Negotiate $respNegotiate")
resp.status = HttpServletResponse.SC_OK

resp.writer.println(context.srcName)
}

System.err.println("Context Established! ")
System.err.println("Client principal is " + context.srcName)
System.err.println("Server principal is " + context.targName)

/*
* If mutual authentication did not take place, then
* only the client was authenticated to the
* server. Otherwise, both client and server were
* authenticated to each other.
*/
if (context.mutualAuthState)
System.err.println("Mutual authentication took place!")
}

@Throws(LoginException::class, PrivilegedActionException::class)
private fun <T> loginAndAction(action: PrivilegedExceptionAction<T>) {
val context = LoginContext("example")
context.login()

// Perform action as authenticated user
val subject = context.subject
println(subject)

Subject.doAs(subject, action)
context.logout()
}
}

日志

Debug is  true storeKey true useTicketCache false useKeyTab true doNotPrompt false ticketCache is null isInitiator true KeyTab is /root/HTTP.keytab refreshKrb5Config is false principal is HTTP/a.example.com@CORP.EXAMPLE.COM tryFirstPass is false useFirstPass is false storePass is false clearPass is false
Looking for keys for: HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (1) for HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (3) for HTTP/a.example.com@CORP.EXAMPLE.COM
Added key: 23version: 2
Added key: 17version: 2
Added key: 18version: 2
Looking for keys for: HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (1) for HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (3) for HTTP/a.example.com@CORP.EXAMPLE.COM
Added key: 23version: 2
Added key: 17version: 2
Added key: 18version: 2
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 20 19 16 23.
>>> KrbAsReq creating message
getKDCFromDNS using UDP
>>> KrbKdcReq send: kdc=dc1.corp.example.com. UDP:88, timeout=30000, number of retries =3, #bytes=175
>>> KDCCommunication: kdc=dc1.corp.example.com. UDP:88, timeout=30000,Attempt =1, #bytes=175
>>> KrbKdcReq send: #bytes read=315
>>>Pre-Authentication Data:
PA-DATA type = 2
PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
PA-DATA type = 16

>>>Pre-Authentication Data:
PA-DATA type = 15

>>>Pre-Authentication Data:
PA-DATA type = 19
PA-ETYPE-INFO2 etype = 18, salt = CORP.EXAMPLE.COMa, s2kparams = 0000: 00 00 10 00 ....

>>> KdcAccessibility: remove dc1.corp.example.com.:88
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
sTime is Thu May 21 20:14:03 UTC 2020 1590092043000
suSec is 748632
error code is 25
error Message is Additional pre-authentication required
crealm is CORP.EXAMPLE.COM
cname is HTTP/a.example.com@CORP.EXAMPLE.COM
sname is krbtgt/CORP.EXAMPLE.COM@CORP.EXAMPLE.COM
eData provided.
msgType is 30
>>>Pre-Authentication Data:
PA-DATA type = 2
PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
PA-DATA type = 16

>>>Pre-Authentication Data:
PA-DATA type = 15

>>>Pre-Authentication Data:
PA-DATA type = 19
PA-ETYPE-INFO2 etype = 18, salt = CORP.EXAMPLE.COMa, s2kparams = 0000: 00 00 10 00 ....

KRBError received: Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ
KrbAsReqBuilder: PREAUTH FAILED/REQ, re-send AS-REQ
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 20 19 16 23.
Looking for keys for: HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (1) for HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (3) for HTTP/a.example.com@CORP.EXAMPLE.COM
Added key: 23version: 2
Added key: 17version: 2
Added key: 18version: 2
Looking for keys for: HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (1) for HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (3) for HTTP/a.example.com@CORP.EXAMPLE.COM
Added key: 23version: 2
Added key: 17version: 2
Added key: 18version: 2
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 20 19 16 23.
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> KrbAsReq creating message
getKDCFromDNS using UDP
>>> KrbKdcReq send: kdc=dc1.corp.example.com. UDP:88, timeout=30000, number of retries =3, #bytes=264
>>> KDCCommunication: kdc=dc1.corp.example.com. UDP:88, timeout=30000,Attempt =1, #bytes=264
>>> KrbKdcReq send: #bytes read=199
>>> KrbKdcReq send: kdc=dc1.corp.example.com. TCP:88, timeout=30000, number of retries =3, #bytes=264
>>> KDCCommunication: kdc=dc1.corp.example.com. TCP:88, timeout=30000,Attempt =1, #bytes=264
>>>DEBUG: TCPClient reading 1511 bytes
>>> KrbKdcReq send: #bytes read=1511
>>> KdcAccessibility: remove dc1.corp.example.com.:88
Looking for keys for: HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (1) for HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (3) for HTTP/a.example.com@CORP.EXAMPLE.COM
Added key: 23version: 2
Added key: 17version: 2
Added key: 18version: 2
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> KrbAsRep cons in KrbAsReq.getReply HTTP/a.example.com
principal is HTTP/a.example.com@CORP.EXAMPLE.COM
Will use keytab
Commit Succeeded

Subject:
Principal: HTTP/a.example.com@CORP.EXAMPLE.COM
Private Credential: Ticket (hex) =
... REDACTED ...

Client Principal = HTTP/a.example.com@CORP.EXAMPLE.COM
Server Principal = krbtgt/CORP.EXAMPLE.COM@CORP.EXAMPLE.COM
Session Key = EncryptionKey: keyType=18 keyBytes (hex dump)=
... REDACTED ...


Forwardable Ticket false
Forwarded Ticket false
Proxiable Ticket false
Proxy Ticket false
Postdated Ticket false
Renewable Ticket false
Initial Ticket true
Auth Time = Thu May 21 20:14:03 UTC 2020
Start Time = Thu May 21 20:14:03 UTC 2020
End Time = Fri May 22 06:14:03 UTC 2020
Renew Till = null
Client Addresses Null
Private Credential: /root/HTTP.keytab for HTTP/a.example.com@CORP.EXAMPLE.COM

Found KeyTab /root/HTTP.keytab for HTTP/a.example.com@CORP.EXAMPLE.COM
Found KeyTab /root/HTTP.keytab for HTTP/a.example.com@CORP.EXAMPLE.COM
Found ticket for HTTP/a.example.com@CORP.EXAMPLE.COM to go to krbtgt/CORP.EXAMPLE.COM@CORP.EXAMPLE.COM expiring on Fri May 22 06:14:03 UTC 2020
[Krb5LoginModule]: Entering logout
[Krb5LoginModule]: logged out Subject
Entered SpNegoContext.acceptSecContext with state=STATE_NEW
SpNegoContext.acceptSecContext: receiving token = ... REDACTED ...
SpNegoToken NegTokenInit: reading Mechanism Oid = 1.2.840.113554.1.2.2
SpNegoToken NegTokenInit: reading Mechanism Oid = 1.2.752.43.14.3
SpNegoToken NegTokenInit: reading Mech Token
SpNegoContext.acceptSecContext: received token of type = SPNEGO NegTokenInit
SpNegoContext: negotiated mechanism = 1.2.840.113554.1.2.2
Entered Krb5Context.acceptSecContext with state=STATE_NEW
Looking for keys for: HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (1) for HTTP/a.example.com@CORP.EXAMPLE.COM
Found unsupported keytype (3) for HTTP/a.example.com@CORP.EXAMPLE.COM
Added key: 23version: 2
Added key: 17version: 2
Added key: 18version: 2
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
Servlet.service() for servlet [HealthServlet] in context with path [] threw exception [Servlet execution threw an exception] with root cause
java.security.GeneralSecurityException: Checksum failed
at java.security.jgss/sun.security.krb5.internal.crypto.dk.AesDkCrypto.decryptCTS(AesDkCrypto.java:451)
at java.security.jgss/sun.security.krb5.internal.crypto.dk.AesDkCrypto.decrypt(AesDkCrypto.java:272)
at java.security.jgss/sun.security.krb5.internal.crypto.Aes256.decrypt(Aes256.java:76)
at java.security.jgss/sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType.decrypt(Aes256CtsHmacSha1EType.java:100)
at java.security.jgss/sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType.decrypt(Aes256CtsHmacSha1EType.java:94)
at java.security.jgss/sun.security.krb5.EncryptedData.decrypt(EncryptedData.java:180)
at java.security.jgss/sun.security.krb5.KrbApReq.authenticate(KrbApReq.java:281)
at java.security.jgss/sun.security.krb5.KrbApReq.<init>(KrbApReq.java:149)
at java.security.jgss/sun.security.jgss.krb5.InitSecContextToken.<init>(InitSecContextToken.java:139)
at java.security.jgss/sun.security.jgss.krb5.Krb5Context.acceptSecContext(Krb5Context.java:832)
at java.security.jgss/sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:361)
at java.security.jgss/sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:303)
at java.security.jgss/sun.security.jgss.spnego.SpNegoContext.GSS_acceptSecContext(SpNegoContext.java:905)
at java.security.jgss/sun.security.jgss.spnego.SpNegoContext.acceptSecContext(SpNegoContext.java:556)
at java.security.jgss/sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:361)
at java.security.jgss/sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:303)
at HealthServlet.doGet(HealthServlet.kt:43)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853)
at org.apache.tomcat.util.net.Nio2Endpoint$SocketProcessor.doRun(Nio2Endpoint.java:1676)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.net.AbstractEndpoint.processSocket(AbstractEndpoint.java:1087)
at org.apache.tomcat.util.net.Nio2Endpoint$Nio2SocketWrapper$2.completed(Nio2Endpoint.java:589)
at org.apache.tomcat.util.net.Nio2Endpoint$Nio2SocketWrapper$2.completed(Nio2Endpoint.java:567)
at java.base/sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:127)
at java.base/sun.nio.ch.Invoker$2.run(Invoker.java:219)
at java.base/sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:834)

最佳答案

原版

到目前为止,我找到的唯一解决方案是修补 Samba,将其回归到旧的 SPN 行为。

diff --git a/source4/heimdal/kdc/kerberos5.c b/source4/heimdal/kdc/kerberos5.c
index 27d38ad84b7..fdf249bc08d 100644
--- a/source4/heimdal/kdc/kerberos5.c
+++ b/source4/heimdal/kdc/kerberos5.c
@@ -762,9 +762,9 @@ kdc_check_flags(krb5_context context,
return KRB5KDC_ERR_POLICY;
}

- if(!client->flags.client){
+ if (!is_as_req && !client->flags.client){
kdc_log(context, config, 0,
- "Principal may not act as client -- %s", client_name);
+ "Principal may only act as client in AS-REQ -- %s", client_name);
return KRB5KDC_ERR_POLICY;
}

@@ -1056,7 +1056,7 @@ _kdc_as_rep(krb5_context context,
*/

ret = _kdc_db_fetch(context, config, client_princ,
- HDB_F_GET_CLIENT | flags, NULL,
+ HDB_F_GET_ANY | flags, NULL,
&clientdb, &client);
if(ret == HDB_ERR_NOT_FOUND_HERE) {
kdc_log(context, config, 5, "client %s does not have secrets at this KDC, need to proxy", client_name);

编辑 1

如提交消息中所述,在 AS-REQ 中使用 SPN 的行为是不正确的。

https://gitlab.com/samba-team/samba/-/commit/a6182bd9512e6c78cfd2127790419418ab776be9

因此,正确的方法是调查 Java 的Checksum failed 异常,而不是修补 Samba。

关于java - Samba 4.11 上使用多个 SPN 和 AES256 的 Kerberos SPNEGO 身份验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61943753/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com