【发布时间】:2023-11-08 12:54:02
【问题描述】:
我有一个用 Kotlin 编写的 HTTP 服务,并使用 Tomcat 来侦听多个域,并且这些域需要通过 Kerberos 进行身份验证。在 Samba 4.9 上,我们有一个用户拥有多个启用了 AES256 加密的 SPN。为该用户生成了一个包含所有 SPN 的密钥表。
升级到 Samba 4.11 后,单个用户中的多个 SPN 停止工作。抛出了错误Client 'HTTP/a.example.com@CORP.EXAMPLE.COM' not found in Kerberos database while getting initial credentials。我们通过创建多个用户来解决此问题,每个 SPN 一个用户并将 UPN 设置为单个 SPN 的值。之后,我们为每个用户生成了 keytab,然后我们将其合并。
问题是,当我收到一张带有aes256-cts-hmac-sha1-96 的票时,java.security.GeneralSecurityException: Checksum failed 被抛出,并且只在一个域中工作,即我用作主体的域。 arcfour-hmac-md5 在所有域上都可以正常工作,但我需要支持 AES 加密。
我已经在我们的旧 Samba 4.9 上测试了这个场景,同样的情况也发生了。如果我们有多个用户,每个用户都有一个 SPN,并且所有用户都有一个 keytab,那么Checksum failed 也会被抛出。
因此,要么我设法让一个具有多个 SPN 的用户在 Samba 4.11 上工作,要么在使用 AES 加密时我必须摆脱Checksum failed。
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)
【问题讨论】:
标签: java kerberos samba spnego