mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
8196584: TLS 1.3 Implementation
Co-authored-by: Adam Petcher <adam.petcher@oracle.com> Co-authored-by: Amanda Jiang <amanda.jiang@oracle.com> Co-authored-by: Anthony Scarpino <anthony.scarpino@oracle.com> Co-authored-by: Bradford Wetmore <bradford.wetmore@oracle.com> Co-authored-by: Jamil Nimeh <jamil.j.nimeh@oracle.com> Co-authored-by: John Jiang <sha.jiang@oracle.com> Co-authored-by: Rajan Halade <rajan.halade@oracle.com> Co-authored-by: Sibabrata Sahoo <sibabrata.sahoo@oracle.com> Co-authored-by: Valerie Peng <valerie.peng@oracle.com> Co-authored-by: Weijun Wang <weijun.wang@oracle.com> Reviewed-by: ascarpino, coffeys, dfuchs, jjiang, jnimeh, mullan, rhalade, ssahoo, valeriep, weijun, wetmore, xuelei
This commit is contained in:
parent
c7c819cd8b
commit
87c6761704
262 changed files with 44368 additions and 32552 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -103,7 +103,7 @@ public class EncryptedData implements Cloneable {
|
|||
return new_encryptedData;
|
||||
}
|
||||
|
||||
// Used in JSSE (com.sun.net.ssl.internal.KerberosPreMasterSecret)
|
||||
// Used by test
|
||||
public EncryptedData(
|
||||
int new_eType,
|
||||
Integer new_kvno,
|
||||
|
@ -126,8 +126,7 @@ public class EncryptedData implements Cloneable {
|
|||
}
|
||||
*/
|
||||
|
||||
// used in KrbApRep, KrbApReq, KrbAsReq, KrbCred, KrbPriv
|
||||
// Used in JSSE (com.sun.net.ssl.internal.KerberosPreMasterSecret)
|
||||
// used in KrbApRep, KrbApReq, KrbAsReq, KrbCred, KrbPriv
|
||||
public EncryptedData(
|
||||
EncryptionKey key,
|
||||
byte[] plaintext,
|
||||
|
|
|
@ -1,269 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.security.krb5.internal.ssl;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
|
||||
import sun.security.krb5.EncryptionKey;
|
||||
import sun.security.krb5.EncryptedData;
|
||||
import sun.security.krb5.KrbException;
|
||||
import sun.security.krb5.internal.crypto.KeyUsage;
|
||||
|
||||
import sun.security.ssl.Debug;
|
||||
import sun.security.ssl.HandshakeInStream;
|
||||
import sun.security.ssl.HandshakeMessage;
|
||||
import sun.security.ssl.ProtocolVersion;
|
||||
|
||||
/**
|
||||
* This is the Kerberos premaster secret in the Kerberos client key
|
||||
* exchange message (CLIENT --> SERVER); it holds the
|
||||
* Kerberos-encrypted pre-master secret. The secret is encrypted using the
|
||||
* Kerberos session key. The padding and size of the resulting message
|
||||
* depends on the session key type, but the pre-master secret is
|
||||
* always exactly 48 bytes.
|
||||
*
|
||||
*/
|
||||
final class KerberosPreMasterSecret {
|
||||
|
||||
private ProtocolVersion protocolVersion; // preMaster [0,1]
|
||||
private byte[] preMaster; // 48 bytes
|
||||
private byte[] encrypted;
|
||||
|
||||
/**
|
||||
* Constructor used by client to generate premaster secret.
|
||||
*
|
||||
* Client randomly creates a pre-master secret and encrypts it
|
||||
* using the Kerberos session key; only the server can decrypt
|
||||
* it, using the session key available in the service ticket.
|
||||
*
|
||||
* @param protocolVersion used to set preMaster[0,1]
|
||||
* @param generator random number generator for generating premaster secret
|
||||
* @param sessionKey Kerberos session key for encrypting premaster secret
|
||||
*/
|
||||
KerberosPreMasterSecret(ProtocolVersion protocolVersion,
|
||||
SecureRandom generator, EncryptionKey sessionKey) throws IOException {
|
||||
|
||||
if (sessionKey.getEType() ==
|
||||
EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) {
|
||||
throw new IOException(
|
||||
"session keys with des3-cbc-hmac-sha1-kd encryption type " +
|
||||
"are not supported for TLS Kerberos cipher suites");
|
||||
}
|
||||
|
||||
this.protocolVersion = protocolVersion;
|
||||
preMaster = generatePreMaster(generator, protocolVersion);
|
||||
|
||||
// Encrypt premaster secret
|
||||
try {
|
||||
EncryptedData eData = new EncryptedData(sessionKey, preMaster,
|
||||
KeyUsage.KU_UNKNOWN);
|
||||
encrypted = eData.getBytes(); // not ASN.1 encoded.
|
||||
|
||||
} catch (KrbException e) {
|
||||
throw (SSLKeyException)new SSLKeyException
|
||||
("Kerberos premaster secret error").initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructor used by server to decrypt encrypted premaster secret.
|
||||
* The protocol version in preMaster[0,1] must match either currentVersion
|
||||
* or clientVersion, otherwise, the premaster secret is set to
|
||||
* a random one to foil possible attack.
|
||||
*
|
||||
* @param currentVersion version of protocol being used
|
||||
* @param clientVersion version requested by client
|
||||
* @param generator random number generator used to generate
|
||||
* bogus premaster secret if premaster secret verification fails
|
||||
* @param input input stream from which to read the encrypted
|
||||
* premaster secret
|
||||
* @param sessionKey Kerberos session key to be used for decryption
|
||||
*/
|
||||
KerberosPreMasterSecret(ProtocolVersion currentVersion,
|
||||
ProtocolVersion clientVersion,
|
||||
SecureRandom generator, byte[] encrypted,
|
||||
EncryptionKey sessionKey) throws IOException {
|
||||
|
||||
if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
|
||||
if (encrypted != null) {
|
||||
Debug.println(System.out,
|
||||
"encrypted premaster secret", encrypted);
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionKey.getEType() ==
|
||||
EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) {
|
||||
throw new IOException(
|
||||
"session keys with des3-cbc-hmac-sha1-kd encryption type " +
|
||||
"are not supported for TLS Kerberos cipher suites");
|
||||
}
|
||||
|
||||
// Decrypt premaster secret
|
||||
try {
|
||||
EncryptedData data = new EncryptedData(sessionKey.getEType(),
|
||||
null /* optional kvno */, encrypted);
|
||||
|
||||
byte[] temp = data.decrypt(sessionKey, KeyUsage.KU_UNKNOWN);
|
||||
if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
|
||||
if (encrypted != null) {
|
||||
Debug.println(System.out,
|
||||
"decrypted premaster secret", temp);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove padding bytes after decryption. Only DES and DES3 have
|
||||
// paddings and we don't support DES3 in TLS (see above)
|
||||
|
||||
if (temp.length == 52 &&
|
||||
data.getEType() == EncryptedData.ETYPE_DES_CBC_CRC) {
|
||||
// For des-cbc-crc, 4 paddings. Value can be 0x04 or 0x00.
|
||||
if (paddingByteIs(temp, 52, (byte)4) ||
|
||||
paddingByteIs(temp, 52, (byte)0)) {
|
||||
temp = Arrays.copyOf(temp, 48);
|
||||
}
|
||||
} else if (temp.length == 56 &&
|
||||
data.getEType() == EncryptedData.ETYPE_DES_CBC_MD5) {
|
||||
// For des-cbc-md5, 8 paddings with 0x08, or no padding
|
||||
if (paddingByteIs(temp, 56, (byte)8)) {
|
||||
temp = Arrays.copyOf(temp, 48);
|
||||
}
|
||||
}
|
||||
|
||||
preMaster = temp;
|
||||
|
||||
protocolVersion = ProtocolVersion.valueOf(preMaster[0],
|
||||
preMaster[1]);
|
||||
if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Kerberos PreMasterSecret version: "
|
||||
+ protocolVersion);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// catch exception & process below
|
||||
preMaster = null;
|
||||
protocolVersion = currentVersion;
|
||||
}
|
||||
|
||||
// check if the premaster secret version is ok
|
||||
// the specification says that it must be the maximum version supported
|
||||
// by the client from its ClientHello message. However, many
|
||||
// old implementations send the negotiated version, so accept both
|
||||
// for SSL v3.0 and TLS v1.0.
|
||||
// NOTE that we may be comparing two unsupported version numbers in
|
||||
// the second case, which is why we cannot use object references
|
||||
// equality in this special case
|
||||
boolean versionMismatch = (protocolVersion.v != clientVersion.v);
|
||||
|
||||
/*
|
||||
* we never checked the client_version in server side
|
||||
* for TLS v1.0 and SSL v3.0. For compatibility, we
|
||||
* maintain this behavior.
|
||||
*/
|
||||
if (versionMismatch && (clientVersion.v <= 0x0301)) {
|
||||
versionMismatch = (protocolVersion.v != currentVersion.v);
|
||||
}
|
||||
|
||||
/*
|
||||
* Bogus decrypted ClientKeyExchange? If so, conjure a
|
||||
* a random preMaster secret that will fail later during
|
||||
* Finished message processing. This is a countermeasure against
|
||||
* the "interactive RSA PKCS#1 encryption envelop attack" reported
|
||||
* in June 1998. Preserving the executation path will
|
||||
* mitigate timing attacks and force consistent error handling
|
||||
* that will prevent an attacking client from differentiating
|
||||
* different kinds of decrypted ClientKeyExchange bogosities.
|
||||
*/
|
||||
if ((preMaster == null) || (preMaster.length != 48)
|
||||
|| versionMismatch) {
|
||||
if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Kerberos PreMasterSecret error, "
|
||||
+ "generating random secret");
|
||||
if (preMaster != null) {
|
||||
Debug.println(System.out, "Invalid secret", preMaster);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Randomize the preMaster secret with the
|
||||
* ClientHello.client_version, as will produce invalid master
|
||||
* secret to prevent the attacks.
|
||||
*/
|
||||
preMaster = generatePreMaster(generator, clientVersion);
|
||||
protocolVersion = clientVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all paddings of data are b
|
||||
* @param data the block with padding
|
||||
* @param len length of data, >= 48
|
||||
* @param b expected padding byte
|
||||
*/
|
||||
private static boolean paddingByteIs(byte[] data, int len, byte b) {
|
||||
for (int i=48; i<len; i++) {
|
||||
if (data[i] != b) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Used by server to generate premaster secret in case of
|
||||
* problem decoding ticket.
|
||||
*
|
||||
* @param protocolVersion used for preMaster[0,1]
|
||||
* @param generator random number generator to use for generating secret.
|
||||
*/
|
||||
KerberosPreMasterSecret(ProtocolVersion protocolVersion,
|
||||
SecureRandom generator) {
|
||||
|
||||
this.protocolVersion = protocolVersion;
|
||||
preMaster = generatePreMaster(generator, protocolVersion);
|
||||
}
|
||||
|
||||
private static byte[] generatePreMaster(SecureRandom rand,
|
||||
ProtocolVersion ver) {
|
||||
|
||||
byte[] pm = new byte[48];
|
||||
rand.nextBytes(pm);
|
||||
pm[0] = ver.major;
|
||||
pm[1] = ver.minor;
|
||||
|
||||
return pm;
|
||||
}
|
||||
|
||||
// Clone not needed; internal use only
|
||||
byte[] getUnencrypted() {
|
||||
return preMaster;
|
||||
}
|
||||
|
||||
// Clone not needed; internal use only
|
||||
byte[] getEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
}
|
|
@ -1,563 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.security.krb5.internal.ssl;
|
||||
|
||||
import sun.security.ssl.ClientKeyExchange;
|
||||
import sun.security.ssl.Debug;
|
||||
import sun.security.ssl.ClientKeyExchangeService;
|
||||
import sun.security.ssl.HandshakeOutStream;
|
||||
|
||||
import sun.security.jgss.GSSCaller;
|
||||
import sun.security.jgss.krb5.Krb5Util;
|
||||
import sun.security.jgss.krb5.ServiceCreds;
|
||||
import sun.security.krb5.EncryptedData;
|
||||
import sun.security.krb5.EncryptionKey;
|
||||
import sun.security.krb5.KrbException;
|
||||
import sun.security.krb5.PrincipalName;
|
||||
import sun.security.krb5.internal.EncTicketPart;
|
||||
import sun.security.krb5.internal.Ticket;
|
||||
import sun.security.krb5.internal.crypto.KeyUsage;
|
||||
import sun.security.ssl.ProtocolVersion;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.kerberos.KerberosKey;
|
||||
import javax.security.auth.kerberos.KerberosPrincipal;
|
||||
import javax.security.auth.kerberos.KerberosTicket;
|
||||
import javax.security.auth.kerberos.KeyTab;
|
||||
import javax.security.auth.kerberos.ServicePermission;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.InetAddress;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The provider for TLS_KRB_ cipher suites.
|
||||
*
|
||||
* @since 9
|
||||
*/
|
||||
public class Krb5KeyExchangeService implements ClientKeyExchangeService {
|
||||
|
||||
public static final Debug debug = Debug.getInstance("ssl");
|
||||
|
||||
@Override
|
||||
public String[] supported() {
|
||||
return new String[] { "KRB5", "KRB5_EXPORT" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getServiceCreds(AccessControlContext acc) {
|
||||
try {
|
||||
ServiceCreds serviceCreds = AccessController.doPrivileged(
|
||||
(PrivilegedExceptionAction<ServiceCreds>)
|
||||
() -> Krb5Util.getServiceCreds(
|
||||
GSSCaller.CALLER_SSL_SERVER, null, acc));
|
||||
if (serviceCreds == null) {
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Kerberos serviceCreds not available");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Using Kerberos creds");
|
||||
}
|
||||
String serverPrincipal = serviceCreds.getName();
|
||||
if (serverPrincipal != null) {
|
||||
// When service is bound, we check ASAP. Otherwise,
|
||||
// will check after client request is received
|
||||
// in in Kerberos ClientKeyExchange
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
try {
|
||||
if (sm != null) {
|
||||
// Eliminate dependency on ServicePermission
|
||||
sm.checkPermission(new ServicePermission(
|
||||
serverPrincipal, "accept"), acc);
|
||||
}
|
||||
} catch (SecurityException se) {
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Permission to access Kerberos"
|
||||
+ " secret key denied");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return serviceCreds;
|
||||
} catch (PrivilegedActionException e) {
|
||||
// Likely exception here is LoginException
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Attempt to obtain Kerberos key failed: "
|
||||
+ e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceHostName(Principal principal) {
|
||||
if (principal == null) {
|
||||
return null;
|
||||
}
|
||||
String hostName = null;
|
||||
try {
|
||||
PrincipalName princName =
|
||||
new PrincipalName(principal.getName(),
|
||||
PrincipalName.KRB_NT_SRV_HST);
|
||||
String[] nameParts = princName.getNameStrings();
|
||||
if (nameParts.length >= 2) {
|
||||
hostName = nameParts[1];
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return hostName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isRelated(boolean isClient,
|
||||
AccessControlContext acc, Principal p) {
|
||||
|
||||
if (p == null) return false;
|
||||
try {
|
||||
Subject subject = AccessController.doPrivileged(
|
||||
(PrivilegedExceptionAction<Subject>)
|
||||
() -> Krb5Util.getSubject(
|
||||
isClient ? GSSCaller.CALLER_SSL_CLIENT
|
||||
: GSSCaller.CALLER_SSL_SERVER,
|
||||
acc));
|
||||
if (subject == null) {
|
||||
if (debug != null && Debug.isOn("session")) {
|
||||
System.out.println("Kerberos credentials are" +
|
||||
" not present in the current Subject;" +
|
||||
" check if " +
|
||||
" javax.security.auth.useSubjectAsCreds" +
|
||||
" system property has been set to false");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Set<Principal> principals =
|
||||
subject.getPrincipals(Principal.class);
|
||||
if (principals.contains(p)) {
|
||||
// bound to this principal
|
||||
return true;
|
||||
} else {
|
||||
if (isClient) {
|
||||
return false;
|
||||
} else {
|
||||
for (KeyTab pc : subject.getPrivateCredentials(KeyTab.class)) {
|
||||
if (!pc.isBound()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (PrivilegedActionException pae) {
|
||||
if (debug != null && Debug.isOn("session")) {
|
||||
System.out.println("Attempt to obtain" +
|
||||
" subject failed! " + pae);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ClientKeyExchange createClientExchange(
|
||||
String serverName, AccessControlContext acc,
|
||||
ProtocolVersion protocolVerson, SecureRandom rand) throws IOException {
|
||||
return new ExchangerImpl(serverName, acc, protocolVerson, rand);
|
||||
}
|
||||
|
||||
public ClientKeyExchange createServerExchange(
|
||||
ProtocolVersion protocolVersion, ProtocolVersion clientVersion,
|
||||
SecureRandom rand, byte[] encodedTicket, byte[] encrypted,
|
||||
AccessControlContext acc, Object serviceCreds) throws IOException {
|
||||
return new ExchangerImpl(protocolVersion, clientVersion, rand,
|
||||
encodedTicket, encrypted, acc, serviceCreds);
|
||||
}
|
||||
|
||||
static class ExchangerImpl extends ClientKeyExchange {
|
||||
|
||||
final private KerberosPreMasterSecret preMaster;
|
||||
final private byte[] encodedTicket;
|
||||
final private KerberosPrincipal peerPrincipal;
|
||||
final private KerberosPrincipal localPrincipal;
|
||||
|
||||
@Override
|
||||
public int messageLength() {
|
||||
return encodedTicket.length + preMaster.getEncrypted().length + 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(HandshakeOutStream s) throws IOException {
|
||||
s.putBytes16(encodedTicket);
|
||||
s.putBytes16(null);
|
||||
s.putBytes16(preMaster.getEncrypted());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(PrintStream s) throws IOException {
|
||||
s.println("*** ClientKeyExchange, Kerberos");
|
||||
|
||||
if (debug != null && Debug.isOn("verbose")) {
|
||||
Debug.println(s, "Kerberos service ticket", encodedTicket);
|
||||
Debug.println(s, "Random Secret", preMaster.getUnencrypted());
|
||||
Debug.println(s, "Encrypted random Secret", preMaster.getEncrypted());
|
||||
}
|
||||
}
|
||||
|
||||
ExchangerImpl(String serverName, AccessControlContext acc,
|
||||
ProtocolVersion protocolVersion, SecureRandom rand) throws IOException {
|
||||
|
||||
// Get service ticket
|
||||
KerberosTicket ticket = getServiceTicket(serverName, acc);
|
||||
encodedTicket = ticket.getEncoded();
|
||||
|
||||
// Record the Kerberos principals
|
||||
peerPrincipal = ticket.getServer();
|
||||
localPrincipal = ticket.getClient();
|
||||
|
||||
// Optional authenticator, encrypted using session key,
|
||||
// currently ignored
|
||||
|
||||
// Generate premaster secret and encrypt it using session key
|
||||
EncryptionKey sessionKey = new EncryptionKey(
|
||||
ticket.getSessionKeyType(),
|
||||
ticket.getSessionKey().getEncoded());
|
||||
|
||||
preMaster = new KerberosPreMasterSecret(protocolVersion,
|
||||
rand, sessionKey);
|
||||
}
|
||||
|
||||
ExchangerImpl(
|
||||
ProtocolVersion protocolVersion, ProtocolVersion clientVersion, SecureRandom rand,
|
||||
byte[] encodedTicket, byte[] encrypted,
|
||||
AccessControlContext acc, Object serviceCreds) throws IOException {
|
||||
|
||||
// Read ticket
|
||||
this.encodedTicket = encodedTicket;
|
||||
|
||||
if (debug != null && Debug.isOn("verbose")) {
|
||||
Debug.println(System.out,
|
||||
"encoded Kerberos service ticket", encodedTicket);
|
||||
}
|
||||
|
||||
EncryptionKey sessionKey = null;
|
||||
KerberosPrincipal tmpPeer = null;
|
||||
KerberosPrincipal tmpLocal = null;
|
||||
|
||||
try {
|
||||
Ticket t = new Ticket(encodedTicket);
|
||||
|
||||
EncryptedData encPart = t.encPart;
|
||||
PrincipalName ticketSname = t.sname;
|
||||
|
||||
final ServiceCreds creds = (ServiceCreds)serviceCreds;
|
||||
final KerberosPrincipal princ =
|
||||
new KerberosPrincipal(ticketSname.toString());
|
||||
|
||||
// For bound service, permission already checked at setup
|
||||
if (creds.getName() == null) {
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
try {
|
||||
if (sm != null) {
|
||||
// Eliminate dependency on ServicePermission
|
||||
sm.checkPermission(new ServicePermission(
|
||||
ticketSname.toString(), "accept"), acc);
|
||||
}
|
||||
} catch (SecurityException se) {
|
||||
serviceCreds = null;
|
||||
// Do not destroy keys. Will affect Subject
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Permission to access Kerberos"
|
||||
+ " secret key denied");
|
||||
se.printStackTrace(System.out);
|
||||
}
|
||||
throw new IOException("Kerberos service not allowedy");
|
||||
}
|
||||
}
|
||||
KerberosKey[] serverKeys = AccessController.doPrivileged(
|
||||
new PrivilegedAction<KerberosKey[]>() {
|
||||
@Override
|
||||
public KerberosKey[] run() {
|
||||
return creds.getKKeys(princ);
|
||||
}
|
||||
});
|
||||
if (serverKeys.length == 0) {
|
||||
throw new IOException("Found no key for " + princ +
|
||||
(creds.getName() == null ? "" :
|
||||
(", this keytab is for " + creds.getName() + " only")));
|
||||
}
|
||||
|
||||
/*
|
||||
* permission to access and use the secret key of the Kerberized
|
||||
* "host" service is done in ServerHandshaker.getKerberosKeys()
|
||||
* to ensure server has the permission to use the secret key
|
||||
* before promising the client
|
||||
*/
|
||||
|
||||
// See if we have the right key to decrypt the ticket to get
|
||||
// the session key.
|
||||
int encPartKeyType = encPart.getEType();
|
||||
Integer encPartKeyVersion = encPart.getKeyVersionNumber();
|
||||
KerberosKey dkey = null;
|
||||
try {
|
||||
dkey = findKey(encPartKeyType, encPartKeyVersion, serverKeys);
|
||||
} catch (KrbException ke) { // a kvno mismatch
|
||||
throw new IOException(
|
||||
"Cannot find key matching version number", ke);
|
||||
}
|
||||
if (dkey == null) {
|
||||
// %%% Should print string repr of etype
|
||||
throw new IOException("Cannot find key of appropriate type" +
|
||||
" to decrypt ticket - need etype " + encPartKeyType);
|
||||
}
|
||||
|
||||
EncryptionKey secretKey = new EncryptionKey(
|
||||
encPartKeyType,
|
||||
dkey.getEncoded());
|
||||
|
||||
// Decrypt encPart using server's secret key
|
||||
byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET);
|
||||
|
||||
// Reset data stream after decryption, remove redundant bytes
|
||||
byte[] temp = encPart.reset(bytes);
|
||||
EncTicketPart encTicketPart = new EncTicketPart(temp);
|
||||
|
||||
// Record the Kerberos Principals
|
||||
tmpPeer = new KerberosPrincipal(encTicketPart.cname.getName());
|
||||
tmpLocal = new KerberosPrincipal(ticketSname.getName());
|
||||
|
||||
sessionKey = encTicketPart.key;
|
||||
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("server principal: " + ticketSname);
|
||||
System.out.println("cname: " + encTicketPart.cname.toString());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("KerberosWrapper error getting session key,"
|
||||
+ " generating random secret (" + e.getMessage() + ")");
|
||||
}
|
||||
sessionKey = null;
|
||||
}
|
||||
|
||||
//input.getBytes16(); // XXX Read and ignore authenticator
|
||||
|
||||
if (sessionKey != null) {
|
||||
preMaster = new KerberosPreMasterSecret(protocolVersion,
|
||||
clientVersion, rand, encrypted, sessionKey);
|
||||
} else {
|
||||
// Generate bogus premaster secret
|
||||
preMaster = new KerberosPreMasterSecret(clientVersion, rand);
|
||||
}
|
||||
|
||||
peerPrincipal = tmpPeer;
|
||||
localPrincipal = tmpLocal;
|
||||
}
|
||||
|
||||
// Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context
|
||||
private static KerberosTicket getServiceTicket(String serverName,
|
||||
final AccessControlContext acc) throws IOException {
|
||||
|
||||
if ("localhost".equals(serverName) ||
|
||||
"localhost.localdomain".equals(serverName)) {
|
||||
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Get the local hostname");
|
||||
}
|
||||
String localHost = java.security.AccessController.doPrivileged(
|
||||
new java.security.PrivilegedAction<String>() {
|
||||
public String run() {
|
||||
try {
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
} catch (java.net.UnknownHostException e) {
|
||||
if (debug != null && Debug.isOn("handshake")) {
|
||||
System.out.println("Warning,"
|
||||
+ " cannot get the local hostname: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (localHost != null) {
|
||||
serverName = localHost;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve serverName (possibly in IP addr form) to Kerberos principal
|
||||
// name for service with hostname
|
||||
String serviceName = "host/" + serverName;
|
||||
PrincipalName principal;
|
||||
try {
|
||||
principal = new PrincipalName(serviceName,
|
||||
PrincipalName.KRB_NT_SRV_HST);
|
||||
} catch (SecurityException se) {
|
||||
throw se;
|
||||
} catch (Exception e) {
|
||||
IOException ioe = new IOException("Invalid service principal" +
|
||||
" name: " + serviceName);
|
||||
ioe.initCause(e);
|
||||
throw ioe;
|
||||
}
|
||||
String realm = principal.getRealmAsString();
|
||||
|
||||
final String serverPrincipal = principal.toString();
|
||||
final String tgsPrincipal = "krbtgt/" + realm + "@" + realm;
|
||||
final String clientPrincipal = null; // use default
|
||||
|
||||
|
||||
// check permission to obtain a service ticket to initiate a
|
||||
// context with the "host" service
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
sm.checkPermission(new ServicePermission(serverPrincipal,
|
||||
"initiate"), acc);
|
||||
}
|
||||
|
||||
try {
|
||||
KerberosTicket ticket = AccessController.doPrivileged(
|
||||
new PrivilegedExceptionAction<KerberosTicket>() {
|
||||
public KerberosTicket run() throws Exception {
|
||||
return Krb5Util.getTicketFromSubjectAndTgs(
|
||||
GSSCaller.CALLER_SSL_CLIENT,
|
||||
clientPrincipal, serverPrincipal,
|
||||
tgsPrincipal, acc);
|
||||
}});
|
||||
|
||||
if (ticket == null) {
|
||||
throw new IOException("Failed to find any kerberos service" +
|
||||
" ticket for " + serverPrincipal);
|
||||
}
|
||||
return ticket;
|
||||
} catch (PrivilegedActionException e) {
|
||||
IOException ioe = new IOException(
|
||||
"Attempt to obtain kerberos service ticket for " +
|
||||
serverPrincipal + " failed!");
|
||||
ioe.initCause(e);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey clientKeyExchange() {
|
||||
byte[] secretBytes = preMaster.getUnencrypted();
|
||||
return new SecretKeySpec(secretBytes, "TlsPremasterSecret");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPeerPrincipal() {
|
||||
return peerPrincipal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getLocalPrincipal() {
|
||||
return localPrincipal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a kvno matches another kvno. Used in the method
|
||||
* findKey(etype, version, keys). Always returns true if either input
|
||||
* is null or zero, in case any side does not have kvno info available.
|
||||
*
|
||||
* Note: zero is included because N/A is not a legal value for kvno
|
||||
* in javax.security.auth.kerberos.KerberosKey. Therefore, the info
|
||||
* that the kvno is N/A might be lost when converting between
|
||||
* EncryptionKey and KerberosKey.
|
||||
*/
|
||||
private static boolean versionMatches(Integer v1, int v2) {
|
||||
if (v1 == null || v1 == 0 || v2 == 0) {
|
||||
return true;
|
||||
}
|
||||
return v1.equals(v2);
|
||||
}
|
||||
|
||||
private static KerberosKey findKey(int etype, Integer version,
|
||||
KerberosKey[] keys) throws KrbException {
|
||||
int ktype;
|
||||
boolean etypeFound = false;
|
||||
|
||||
// When no matched kvno is found, returns tke key of the same
|
||||
// etype with the highest kvno
|
||||
int kvno_found = 0;
|
||||
KerberosKey key_found = null;
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
ktype = keys[i].getKeyType();
|
||||
if (etype == ktype) {
|
||||
int kv = keys[i].getVersionNumber();
|
||||
etypeFound = true;
|
||||
if (versionMatches(version, kv)) {
|
||||
return keys[i];
|
||||
} else if (kv > kvno_found) {
|
||||
key_found = keys[i];
|
||||
kvno_found = kv;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Key not found.
|
||||
// %%% kludge to allow DES keys to be used for diff etypes
|
||||
if ((etype == EncryptedData.ETYPE_DES_CBC_CRC ||
|
||||
etype == EncryptedData.ETYPE_DES_CBC_MD5)) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
ktype = keys[i].getKeyType();
|
||||
if (ktype == EncryptedData.ETYPE_DES_CBC_CRC ||
|
||||
ktype == EncryptedData.ETYPE_DES_CBC_MD5) {
|
||||
int kv = keys[i].getVersionNumber();
|
||||
etypeFound = true;
|
||||
if (versionMatches(version, kv)) {
|
||||
return new KerberosKey(keys[i].getPrincipal(),
|
||||
keys[i].getEncoded(),
|
||||
etype,
|
||||
kv);
|
||||
} else if (kv > kvno_found) {
|
||||
key_found = new KerberosKey(keys[i].getPrincipal(),
|
||||
keys[i].getEncoded(),
|
||||
etype,
|
||||
kv);
|
||||
kvno_found = kv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (etypeFound) {
|
||||
return key_found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue