public void processServerCredentials(TlsCredentials serverCredentials) throws IOException { // TODO[tls-ops] Process the server certificate differently on the server side processServerCertificate(serverCredentials.getCertificate()); }
private void processHandshakeMessage(short type, byte[] buf) throws IOException { ByteArrayInputStream is = new ByteArrayInputStream(buf); switch (type) { case HandshakeType.certificate: { switch (connection_state) { case CS_SERVER_HELLO_RECEIVED: { // Parse the Certificate message and send to cipher suite Certificate serverCertificate = Certificate.parse(is); assertEmpty(is); this.keyExchange.processServerCertificate(serverCertificate); this.authentication = tlsClient.getAuthentication(); this.authentication.notifyServerCertificate(serverCertificate); break; } default: this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); } connection_state = CS_SERVER_CERTIFICATE_RECEIVED; break; } case HandshakeType.finished: switch (connection_state) { case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED: /* * Read the checksum from the finished message, it has always 12 * bytes for TLS 1.0 and 36 for SSLv3. */ boolean isTls = tlsClientContext.getServerVersion().getFullVersion() >= ProtocolVersion.TLSv10.getFullVersion(); int checksumLength = isTls ? 12 : 36; byte[] serverVerifyData = new byte[checksumLength]; TlsUtils.readFully(serverVerifyData, is); assertEmpty(is); /* * Calculate our own checksum. */ byte[] expectedServerVerifyData = TlsUtils.calculateVerifyData( tlsClientContext, "server finished", rs.getCurrentHash(TlsUtils.SSL_SERVER)); /* * Compare both checksums. */ if (!Arrays.constantTimeAreEqual(expectedServerVerifyData, serverVerifyData)) { /* * Wrong checksum in the finished message. */ this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); } connection_state = CS_DONE; /* * We are now ready to receive application data. */ this.appDataReady = true; break; default: this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); } break; case HandshakeType.server_hello: switch (connection_state) { case CS_CLIENT_HELLO_SEND: /* * Read the server hello message */ ProtocolVersion server_version = TlsUtils.readVersion(is); ProtocolVersion client_version = this.tlsClientContext.getClientVersion(); if (server_version.getFullVersion() > client_version.getFullVersion()) { this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); } this.tlsClientContext.setServerVersion(server_version); this.tlsClient.notifyServerVersion(server_version); /* * Read the server random */ securityParameters.serverRandom = new byte[32]; TlsUtils.readFully(securityParameters.serverRandom, is); byte[] sessionID = TlsUtils.readOpaque8(is); if (sessionID.length > 32) { this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); } this.tlsClient.notifySessionID(sessionID); /* * Find out which CipherSuite the server has chosen and check that * it was one of the offered ones. */ int selectedCipherSuite = TlsUtils.readUint16(is); if (!arrayContains(offeredCipherSuites, selectedCipherSuite) || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) { this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); } this.tlsClient.notifySelectedCipherSuite(selectedCipherSuite); /* * Find out which CompressionMethod the server has chosen and check that * it was one of the offered ones. */ short selectedCompressionMethod = TlsUtils.readUint8(is); if (!arrayContains(offeredCompressionMethods, selectedCompressionMethod)) { this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); } this.tlsClient.notifySelectedCompressionMethod(selectedCompressionMethod); /* * RFC3546 2.2 The extended server hello message format MAY be * sent in place of the server hello message when the client has * requested extended functionality via the extended client hello * message specified in Section 2.1. ... Note that the extended * server hello message is only sent in response to an extended * client hello message. This prevents the possibility that the * extended server hello message could "break" existing TLS 1.0 * clients. */ /* * TODO RFC 3546 2.3 If [...] the older session is resumed, then * the server MUST ignore extensions appearing in the client * hello, and send a server hello containing no extensions. */ // Integer -> byte[] Hashtable serverExtensions = new Hashtable(); if (is.available() > 0) { // Process extensions from extended server hello byte[] extBytes = TlsUtils.readOpaque16(is); ByteArrayInputStream ext = new ByteArrayInputStream(extBytes); while (ext.available() > 0) { Integer extType = Integers.valueOf(TlsUtils.readUint16(ext)); byte[] extValue = TlsUtils.readOpaque16(ext); /* * RFC 5746 Note that sending a "renegotiation_info" * extension in response to a ClientHello containing only * the SCSV is an explicit exception to the prohibition in * RFC 5246, Section 7.4.1.4, on the server sending * unsolicited extensions and is only allowed because the * client is signaling its willingness to receive the * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV * SCSV. TLS implementations MUST continue to comply with * Section 7.4.1.4 for all other extensions. */ if (!extType.equals(EXT_RenegotiationInfo) && clientExtensions.get(extType) == null) { /* * RFC 3546 2.3 Note that for all extension types * (including those defined in future), the extension * type MUST NOT appear in the extended server hello * unless the same extension type appeared in the * corresponding client hello. Thus clients MUST abort * the handshake if they receive an extension type in * the extended server hello that they did not request * in the associated (extended) client hello. */ this.failWithError(AlertLevel.fatal, AlertDescription.unsupported_extension); } if (serverExtensions.containsKey(extType)) { /* * RFC 3546 2.3 Also note that when multiple * extensions of different types are present in the * extended client hello or the extended server hello, * the extensions may appear in any order. There MUST * NOT be more than one extension of the same type. */ this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); } serverExtensions.put(extType, extValue); } } assertEmpty(is); /* * RFC 5746 3.4. When a ServerHello is received, the client MUST * check if it includes the "renegotiation_info" extension: */ { boolean secure_negotiation = serverExtensions.containsKey(EXT_RenegotiationInfo); /* * If the extension is present, set the secure_renegotiation * flag to TRUE. The client MUST then verify that the length * of the "renegotiated_connection" field is zero, and if it * is not, MUST abort the handshake (by sending a fatal * handshake_failure alert). */ if (secure_negotiation) { byte[] renegExtValue = (byte[]) serverExtensions.get(EXT_RenegotiationInfo); if (!Arrays.constantTimeAreEqual( renegExtValue, createRenegotiationInfo(emptybuf))) { this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); } } tlsClient.notifySecureRenegotiation(secure_negotiation); } if (clientExtensions != null) { tlsClient.processServerExtensions(serverExtensions); } this.keyExchange = tlsClient.getKeyExchange(); connection_state = CS_SERVER_HELLO_RECEIVED; break; default: this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); } break; case HandshakeType.server_hello_done: switch (connection_state) { case CS_SERVER_HELLO_RECEIVED: // There was no server certificate message; check it's OK this.keyExchange.skipServerCertificate(); this.authentication = null; // NB: Fall through to next case label case CS_SERVER_CERTIFICATE_RECEIVED: // There was no server key exchange message; check it's OK this.keyExchange.skipServerKeyExchange(); // NB: Fall through to next case label case CS_SERVER_KEY_EXCHANGE_RECEIVED: case CS_CERTIFICATE_REQUEST_RECEIVED: assertEmpty(is); connection_state = CS_SERVER_HELLO_DONE_RECEIVED; TlsCredentials clientCreds = null; if (certificateRequest == null) { this.keyExchange.skipClientCredentials(); } else { clientCreds = this.authentication.getClientCredentials(certificateRequest); if (clientCreds == null) { this.keyExchange.skipClientCredentials(); boolean isTls = tlsClientContext.getServerVersion().getFullVersion() >= ProtocolVersion.TLSv10.getFullVersion(); if (isTls) { sendClientCertificate(Certificate.EMPTY_CHAIN); } else { sendAlert(AlertLevel.warning, AlertDescription.no_certificate); } } else { this.keyExchange.processClientCredentials(clientCreds); sendClientCertificate(clientCreds.getCertificate()); } } /* * Send the client key exchange message, depending on the key * exchange we are using in our CipherSuite. */ sendClientKeyExchange(); connection_state = CS_CLIENT_KEY_EXCHANGE_SEND; /* * Calculate the master_secret */ byte[] pms = this.keyExchange.generatePremasterSecret(); securityParameters.masterSecret = TlsUtils.calculateMasterSecret(this.tlsClientContext, pms); // TODO Is there a way to ensure the data is really overwritten? /* * RFC 2246 8.1. The pre_master_secret should be deleted from * memory once the master_secret has been computed. */ Arrays.fill(pms, (byte) 0); if (clientCreds != null && clientCreds instanceof TlsSignerCredentials) { TlsSignerCredentials signerCreds = (TlsSignerCredentials) clientCreds; byte[] md5andsha1 = rs.getCurrentHash(null); byte[] clientCertificateSignature = signerCreds.generateCertificateSignature(md5andsha1); sendCertificateVerify(clientCertificateSignature); connection_state = CS_CERTIFICATE_VERIFY_SEND; } /* * Now, we send change cipher state */ byte[] cmessage = new byte[1]; cmessage[0] = 1; rs.writeMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.length); connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND; /* * Initialize our cipher suite */ rs.clientCipherSpecDecided(tlsClient.getCompression(), tlsClient.getCipher()); /* * Send our finished message. */ byte[] clientVerifyData = TlsUtils.calculateVerifyData( tlsClientContext, "client finished", rs.getCurrentHash(TlsUtils.SSL_CLIENT)); ByteArrayOutputStream bos = new ByteArrayOutputStream(); TlsUtils.writeUint8(HandshakeType.finished, bos); TlsUtils.writeOpaque24(clientVerifyData, bos); byte[] message = bos.toByteArray(); rs.writeMessage(ContentType.handshake, message, 0, message.length); this.connection_state = CS_CLIENT_FINISHED_SEND; break; default: this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); } break; case HandshakeType.server_key_exchange: { switch (connection_state) { case CS_SERVER_HELLO_RECEIVED: // There was no server certificate message; check it's OK this.keyExchange.skipServerCertificate(); this.authentication = null; // NB: Fall through to next case label case CS_SERVER_CERTIFICATE_RECEIVED: this.keyExchange.processServerKeyExchange(is); assertEmpty(is); break; default: this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); } this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED; break; } case HandshakeType.certificate_request: { switch (connection_state) { case CS_SERVER_CERTIFICATE_RECEIVED: // There was no server key exchange message; check it's OK this.keyExchange.skipServerKeyExchange(); // NB: Fall through to next case label case CS_SERVER_KEY_EXCHANGE_RECEIVED: { if (this.authentication == null) { /* * RFC 2246 7.4.4. It is a fatal handshake_failure alert * for an anonymous server to request client identification. */ this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); } int numTypes = TlsUtils.readUint8(is); short[] certificateTypes = new short[numTypes]; for (int i = 0; i < numTypes; ++i) { certificateTypes[i] = TlsUtils.readUint8(is); } byte[] authorities = TlsUtils.readOpaque16(is); assertEmpty(is); Vector authorityDNs = new Vector(); ByteArrayInputStream bis = new ByteArrayInputStream(authorities); while (bis.available() > 0) { byte[] dnBytes = TlsUtils.readOpaque16(bis); authorityDNs.addElement( X500Name.getInstance(ASN1Primitive.fromByteArray(dnBytes))); } this.certificateRequest = new CertificateRequest(certificateTypes, authorityDNs); this.keyExchange.validateCertificateRequest(this.certificateRequest); break; } default: this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); } this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED; break; } case HandshakeType.hello_request: /* * RFC 2246 7.4.1.1 Hello request This message will be ignored by the * client if the client is currently negotiating a session. This message * may be ignored by the client if it does not wish to renegotiate a * session, or the client may, if it wishes, respond with a * no_renegotiation alert. */ if (connection_state == CS_DONE) { // Renegotiation not supported yet sendAlert(AlertLevel.warning, AlertDescription.no_renegotiation); } break; case HandshakeType.client_key_exchange: case HandshakeType.certificate_verify: case HandshakeType.client_hello: default: // We do not support this! this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); break; } }