/*
 * Copyright (c) 2020, Azul Systems, Inc. All rights reserved.
 * Copyright (c) 2020, Red Hat, Inc.
 * 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.ssl;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLHandshakeException;

import java.io.IOException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

final class KrbKeyExchange {
    static final SSLPossessionGenerator poGenerator =
            new KrbPossessionGenerator();
    static final SSLKeyAgreementGenerator kaGenerator =
            new KrbKAGenerator();

    static final
            class KrbPossessionGenerator implements SSLPossessionGenerator {
        
Kerberos service credentials. Having this possession in the server side will enable the use of a KRB cipher suite in T12ServerHelloProducer::chooseCipherSuite.
/** * Kerberos service credentials. Having this possession in * the server side will enable the use of a KRB cipher suite in * T12ServerHelloProducer::chooseCipherSuite. */
@Override public SSLPossession createPossession(HandshakeContext handshakeContext) { Object serviceCreds = null; try { final AccessControlContext acc = handshakeContext.conContext.acc; serviceCreds = AccessController.doPrivileged( // Eliminate dependency on KerberosKey new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { // get kerberos key for the default principal return Krb5Helper.getServiceCreds(acc); }}); // check permission to access and use the secret key of the // Kerberized "host" service if (serviceCreds != null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Using Kerberos creds"); } String serverPrincipal = Krb5Helper.getServerPrincipalName(serviceCreds); if (serverPrincipal != null) { // When service is bound, we check ASAP. Otherwise, // will check after client request is received // in Kerberos ClientKeyExchange SecurityManager sm = System.getSecurityManager(); try { if (sm != null) { // Eliminate dependency on ServicePermission sm.checkPermission(Krb5Helper.getServicePermission( serverPrincipal, "accept"), acc); } } catch (SecurityException se) { // Do not destroy keys. Will affect Subject if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Permission to access Kerberos" + " secret key denied"); } return null; } } return new KrbServiceCreds(serviceCreds); } } catch (PrivilegedActionException e) { // Likely exception here is LoginException if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Attempt to obtain Kerberos key failed: " + e.toString()); } } return null; } } static final class KrbServiceCreds implements SSLPossession { final Object serviceCreds; KrbServiceCreds(Object serviceCreds) { this.serviceCreds = serviceCreds; } } static final class KrbPremasterSecret implements SSLPossession, SSLCredentials { final byte[] preMaster; // 48 bytes KrbPremasterSecret(byte[] premasterSecret) { preMaster = premasterSecret; } static KrbPremasterSecret createPremasterSecret( ProtocolVersion protocolVersion, SecureRandom rand) { byte[] pm = new byte[48]; rand.nextBytes(pm); pm[0] = protocolVersion.major; pm[1] = protocolVersion.minor; return new KrbPremasterSecret(pm); } static KrbPremasterSecret decode(ProtocolVersion protocolVersion, ProtocolVersion clientVersion, byte[] preMaster, SecureRandom rand) { KrbPremasterSecret preMasterSecret = null; boolean versionMismatch = true; ProtocolVersion preMasterProtocolVersion = null; if (preMaster != null && preMaster.length == 48) { preMasterProtocolVersion = ProtocolVersion.valueOf(preMaster[0], preMaster[1]); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Kerberos pre-master secret protocol" + " version: " + preMasterProtocolVersion); } // Check if the pre-master secret protocol version is valid. // The specification states 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. versionMismatch = (preMasterProtocolVersion.compare(clientVersion) != 0); if (versionMismatch && (clientVersion.compare(ProtocolVersion.TLS10) <= 0)) { versionMismatch = (preMasterProtocolVersion.compare(protocolVersion) != 0); } } if (versionMismatch) { /* * Bogus decrypted ClientKeyExchange? If so, conjure a * a random pre-master 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 execution path will * mitigate timing attacks and force consistent error handling. */ preMasterSecret = createPremasterSecret(clientVersion, rand); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Kerberos pre-master secret error," + " generating random secret for safe failure."); } } else { preMasterSecret = new KrbPremasterSecret(preMaster); } return preMasterSecret; } } private static final class KrbKAGenerator implements SSLKeyAgreementGenerator { // Prevent instantiation of this class. private KrbKAGenerator() { // blank } @Override public SSLKeyDerivation createKeyDerivation( HandshakeContext context) throws IOException { KrbPremasterSecret premaster = null; if (context instanceof ClientHandshakeContext) { for (SSLPossession possession : context.handshakePossessions) { if (possession instanceof KrbPremasterSecret) { premaster = (KrbPremasterSecret)possession; break; } } } else { for (SSLCredentials credential : context.handshakeCredentials) { if (credential instanceof KrbPremasterSecret) { premaster = (KrbPremasterSecret)credential; break; } } } if (premaster == null) { throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE, "No sufficient KRB key agreement parameters negotiated"); } return new KRBKAKeyDerivation(context, premaster.preMaster); } private static final class KRBKAKeyDerivation implements SSLKeyDerivation { private final HandshakeContext context; private final byte[] secretBytes; KRBKAKeyDerivation(HandshakeContext context, byte[] secret) { this.context = context; this.secretBytes = secret; } @Override public SecretKey deriveKey(String algorithm, AlgorithmParameterSpec params) throws IOException { try { SecretKey preMasterSecret = new SecretKeySpec(secretBytes, "TlsPremasterSecret"); SSLMasterKeyDerivation mskd = SSLMasterKeyDerivation.valueOf( context.negotiatedProtocol); if (mskd == null) { // unlikely throw new SSLHandshakeException( "No expected master key derivation for protocol: " + context.negotiatedProtocol.name); } SSLKeyDerivation kd = mskd.createKeyDerivation( context, preMasterSecret); return kd.deriveKey("MasterSecret", params); } catch (Exception gse) { throw (SSLHandshakeException) new SSLHandshakeException( "Could not generate secret").initCause(gse); } } } } }