public byte[] decodeCiphertext(short type, byte[] ciphertext, int offset, int len) throws IOException { int blockSize = decryptCipher.getBlockSize(); int macSize = readMac.getSize(); /* * TODO[TLS 1.1] Explicit IV implies minLen = blockSize + max(blockSize, macSize + 1), * and will need further changes to offset and plen variables below. */ int minLen = Math.max(blockSize, macSize + 1); if (len < minLen) { throw new TlsFatalAlert(AlertDescription.decode_error); } if (len % blockSize != 0) { throw new TlsFatalAlert(AlertDescription.decryption_failed); } for (int i = 0; i < len; i += blockSize) { decryptCipher.processBlock(ciphertext, offset + i, ciphertext, offset + i); } int plen = len; // If there's anything wrong with the padding, this will return zero int totalPad = checkPaddingConstantTime(ciphertext, offset, plen, blockSize, macSize); int macInputLen = plen - totalPad - macSize; byte[] decryptedMac = Arrays.copyOfRange(ciphertext, offset + macInputLen, offset + macInputLen + macSize); byte[] calculatedMac = readMac.calculateMacConstantTime( type, ciphertext, offset, macInputLen, plen - macSize, randomData); boolean badMac = !Arrays.constantTimeAreEqual(calculatedMac, decryptedMac); if (badMac || totalPad == 0) { throw new TlsFatalAlert(AlertDescription.bad_record_mac); } return Arrays.copyOfRange(ciphertext, offset, offset + macInputLen); }
/** @deprecated */ private boolean verifyDigest(byte[] digest, PublicKey key, byte[] signature, Provider sigProvider) throws NoSuchAlgorithmException, CMSException { String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID()); try { if (encName.equals("RSA")) { Cipher c = CMSEnvelopedHelper.INSTANCE.createAsymmetricCipher("RSA/ECB/PKCS1Padding", sigProvider); c.init(Cipher.DECRYPT_MODE, key); DigestInfo digInfo = derDecode(c.doFinal(signature)); if (!digInfo.getAlgorithmId().getObjectId().equals(digestAlgorithm.getObjectId())) { return false; } if (!isNull(digInfo.getAlgorithmId().getParameters())) { return false; } byte[] sigHash = digInfo.getDigest(); return Arrays.constantTimeAreEqual(digest, sigHash); } else if (encName.equals("DSA")) { Signature sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEwithDSA", sigProvider); sig.initVerify(key); sig.update(digest); return sig.verify(signature); } else { throw new CMSException("algorithm: " + encName + " not supported in base signatures."); } } catch (GeneralSecurityException e) { throw new CMSException("Exception processing signature: " + e, e); } catch (IOException e) { throw new CMSException("Exception decoding signature: " + e, e); } }
/** * @param key * @param checksum * @return true if okay, false otherwise. * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum */ private boolean checkCMSKeyChecksum(byte[] key, byte[] checksum) { return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum); }
/** * Process a packet of data for either CCM decryption or encryption. * * @param in data for processing. * @param inOff offset at which data starts in the input array. * @param inLen length of the data in the input array. * @param output output array. * @param outOff offset into output array to start putting processed bytes. * @return the number of bytes added to output. * @throws IllegalStateException if the cipher is not appropriately set up. * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. * @throws DataLengthException if output buffer too short. */ public int processPacket(byte[] in, int inOff, int inLen, byte[] output, int outOff) throws IllegalStateException, InvalidCipherTextException, DataLengthException { // TODO: handle null keyParam (e.g. via RepeatedKeySpec) // Need to keep the CTR and CBC Mac parts around and reset if (keyParam == null) { throw new IllegalStateException("CCM cipher unitialized."); } int n = nonce.length; int q = 15 - n; if (q < 4) { int limitLen = 1 << (8 * q); if (inLen >= limitLen) { throw new IllegalStateException("CCM packet too large for choice of q."); } } byte[] iv = new byte[blockSize]; iv[0] = (byte) ((q - 1) & 0x7); System.arraycopy(nonce, 0, iv, 1, nonce.length); BlockCipher ctrCipher = new SICBlockCipher(cipher); ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv)); int outputLen; int inIndex = inOff; int outIndex = outOff; if (forEncryption) { outputLen = inLen + macSize; if (output.length < (outputLen + outOff)) { throw new DataLengthException("Output buffer too short."); } calculateMac(in, inOff, inLen, macBlock); ctrCipher.processBlock(macBlock, 0, macBlock, 0); // S0 while (inIndex < (inOff + inLen - blockSize)) // S1... { ctrCipher.processBlock(in, inIndex, output, outIndex); outIndex += blockSize; inIndex += blockSize; } byte[] block = new byte[blockSize]; System.arraycopy(in, inIndex, block, 0, inLen + inOff - inIndex); ctrCipher.processBlock(block, 0, block, 0); System.arraycopy(block, 0, output, outIndex, inLen + inOff - inIndex); System.arraycopy(macBlock, 0, output, outOff + inLen, macSize); } else { if (inLen < macSize) { throw new InvalidCipherTextException("data too short"); } outputLen = inLen - macSize; if (output.length < (outputLen + outOff)) { throw new DataLengthException("Output buffer too short."); } System.arraycopy(in, inOff + outputLen, macBlock, 0, macSize); ctrCipher.processBlock(macBlock, 0, macBlock, 0); for (int i = macSize; i != macBlock.length; i++) { macBlock[i] = 0; } while (inIndex < (inOff + outputLen - blockSize)) { ctrCipher.processBlock(in, inIndex, output, outIndex); outIndex += blockSize; inIndex += blockSize; } byte[] block = new byte[blockSize]; System.arraycopy(in, inIndex, block, 0, outputLen - (inIndex - inOff)); ctrCipher.processBlock(block, 0, block, 0); System.arraycopy(block, 0, output, outIndex, outputLen - (inIndex - inOff)); byte[] calculatedMacBlock = new byte[blockSize]; calculateMac(output, outOff, outputLen, calculatedMacBlock); if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock)) { throw new InvalidCipherTextException("mac check in CCM failed"); } } return outputLen; }
protected void processClientHello(ServerHandshakeState state, byte[] body) throws IOException { ByteArrayInputStream buf = new ByteArrayInputStream(body); // TODO Read RFCs for guidance on the expected record layer version number ProtocolVersion client_version = TlsUtils.readVersion(buf); if (!client_version.isDTLS()) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } /* * Read the client random */ byte[] client_random = TlsUtils.readFully(32, buf); byte[] sessionID = TlsUtils.readOpaque8(buf); if (sessionID.length > 32) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347 byte[] cookie = TlsUtils.readOpaque8(buf); int cipher_suites_length = TlsUtils.readUint16(buf); if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0) { throw new TlsFatalAlert(AlertDescription.decode_error); } /* * NOTE: "If the session_id field is not empty (implying a session resumption request) this * vector must include at least the cipher_suite from that session." */ state.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf); int compression_methods_length = TlsUtils.readUint8(buf); if (compression_methods_length < 1) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } state.offeredCompressionMethods = TlsUtils.readUint8Array(compression_methods_length, buf); /* * 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. */ state.clientExtensions = TlsProtocol.readExtensions(buf); state.serverContext.setClientVersion(client_version); state.server.notifyClientVersion(client_version); state.serverContext.getSecurityParameters().clientRandom = client_random; state.server.notifyOfferedCipherSuites(state.offeredCipherSuites); state.server.notifyOfferedCompressionMethods(state.offeredCompressionMethods); /* * RFC 5746 3.6. Server Behavior: Initial Handshake */ { /* * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the * ClientHello. Including both is NOT RECOMMENDED. */ /* * When a ClientHello is received, the server MUST check if it includes the * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag * to TRUE. */ if (Arrays.contains( state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) { state.secure_renegotiation = true; } /* * The server MUST check if the "renegotiation_info" extension is included in the * ClientHello. */ byte[] renegExtData = TlsUtils.getExtensionData(state.clientExtensions, TlsProtocol.EXT_RenegotiationInfo); if (renegExtData != null) { /* * If the extension is present, set secure_renegotiation flag to TRUE. The * server MUST then verify that the length of the "renegotiated_connection" * field is zero, and if it is not, MUST abort the handshake. */ state.secure_renegotiation = true; if (!Arrays.constantTimeAreEqual( renegExtData, TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) { throw new TlsFatalAlert(AlertDescription.handshake_failure); } } } state.server.notifySecureRenegotiation(state.secure_renegotiation); if (state.clientExtensions != null) { state.server.processClientExtensions(state.clientExtensions); } }
private boolean doVerify(SignerInformationVerifier verifier) throws CMSException { String digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(this.getDigestAlgOID()); String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID()); String signatureName = digestName + "with" + encName; try { if (digestCalculator != null) { resultDigest = digestCalculator.getDigest(); } else { DigestCalculator calc = verifier.getDigestCalculator(this.getDigestAlgorithmID()); if (content != null) { OutputStream digOut = calc.getOutputStream(); content.write(digOut); digOut.close(); } else if (signedAttributeSet == null) { // TODO Get rid of this exception and just treat content==null as empty not missing? throw new CMSException("data not encapsulated in signature - use detached constructor."); } resultDigest = calc.getDigest(); } } catch (IOException e) { throw new CMSException("can't process mime object to create signature.", e); } catch (NoSuchAlgorithmException e) { throw new CMSException("can't find algorithm: " + e.getMessage(), e); } catch (OperatorCreationException e) { throw new CMSException("can't create digest calculator: " + e.getMessage(), e); } // RFC 3852 11.1 Check the content-type attribute is correct { DERObject validContentType = getSingleValuedSignedAttribute(CMSAttributes.contentType, "content-type"); if (validContentType == null) { if (!isCounterSignature && signedAttributeSet != null) { throw new CMSException( "The content-type attribute type MUST be present whenever signed attributes are present in signed-data"); } } else { if (isCounterSignature) { throw new CMSException( "[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute"); } if (!(validContentType instanceof DERObjectIdentifier)) { throw new CMSException( "content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'"); } DERObjectIdentifier signedContentType = (DERObjectIdentifier) validContentType; if (!signedContentType.equals(contentType)) { throw new CMSException("content-type attribute value does not match eContentType"); } } } // RFC 3852 11.2 Check the message-digest attribute is correct { DERObject validMessageDigest = getSingleValuedSignedAttribute(CMSAttributes.messageDigest, "message-digest"); if (validMessageDigest == null) { if (signedAttributeSet != null) { throw new CMSException( "the message-digest signed attribute type MUST be present when there are any signed attributes present"); } } else { if (!(validMessageDigest instanceof ASN1OctetString)) { throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'"); } ASN1OctetString signedMessageDigest = (ASN1OctetString) validMessageDigest; if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets())) { throw new CMSSignerDigestMismatchException( "message-digest attribute value does not match calculated value"); } } } // RFC 3852 11.4 Validate countersignature attribute(s) { AttributeTable signedAttrTable = this.getSignedAttributes(); if (signedAttrTable != null && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0) { throw new CMSException("A countersignature attribute MUST NOT be a signed attribute"); } AttributeTable unsignedAttrTable = this.getUnsignedAttributes(); if (unsignedAttrTable != null) { ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature); for (int i = 0; i < csAttrs.size(); ++i) { Attribute csAttr = (Attribute) csAttrs.get(i); if (csAttr.getAttrValues().size() < 1) { throw new CMSException( "A countersignature attribute MUST contain at least one AttributeValue"); } // Note: We don't recursively validate the countersignature value } } } try { ContentVerifier contentVerifier = verifier.getContentVerifier(sigAlgFinder.find(signatureName)); OutputStream sigOut = contentVerifier.getOutputStream(); if (signedAttributeSet == null) { if (digestCalculator != null) { if (contentVerifier instanceof RawContentVerifier) { RawContentVerifier rawVerifier = (RawContentVerifier) contentVerifier; if (encName.equals("RSA")) { DigestInfo digInfo = new DigestInfo(digestAlgorithm, resultDigest); return rawVerifier.verify(digInfo.getDEREncoded(), this.getSignature()); } return rawVerifier.verify(resultDigest, this.getSignature()); } throw new CMSException("verifier unable to process raw signature"); } else if (content != null) { // TODO Use raw signature of the hash value instead content.write(sigOut); } } else { sigOut.write(this.getEncodedSignedAttributes()); } sigOut.close(); return contentVerifier.verify(this.getSignature()); } catch (IOException e) { throw new CMSException("can't process mime object to create signature.", e); } catch (OperatorCreationException e) { throw new CMSException("can't create content verifier: " + e.getMessage(), e); } }
/** @deprecated */ private boolean doVerify(PublicKey key, Provider sigProvider) throws CMSException, NoSuchAlgorithmException { String digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(this.getDigestAlgOID()); String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID()); String signatureName = digestName + "with" + encName; Signature sig = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, sigProvider); MessageDigest digest = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider); // TODO [BJA-109] Note: PSSParameterSpec requires JDK1.4+ /* try { DERObjectIdentifier sigAlgOID = encryptionAlgorithm.getObjectId(); DEREncodable sigParams = this.encryptionAlgorithm.getParameters(); if (sigAlgOID.equals(PKCSObjectIdentifiers.id_RSASSA_PSS)) { // RFC 4056 // When the id-RSASSA-PSS algorithm identifier is used for a signature, // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params. if (sigParams == null) { throw new CMSException( "RSASSA-PSS signature must specify algorithm parameters"); } AlgorithmParameters params = AlgorithmParameters.getInstance( sigAlgOID.getId(), sig.getProvider().getName()); params.init(sigParams.getDERObject().getEncoded(), "ASN.1"); PSSParameterSpec spec = (PSSParameterSpec)params.getParameterSpec(PSSParameterSpec.class); sig.setParameter(spec); } else { // TODO Are there other signature algorithms that provide parameters? if (sigParams != null) { throw new CMSException("unrecognised signature parameters provided"); } } } catch (IOException e) { throw new CMSException("error encoding signature parameters.", e); } catch (InvalidAlgorithmParameterException e) { throw new CMSException("error setting signature parameters.", e); } catch (InvalidParameterSpecException e) { throw new CMSException("error processing signature parameters.", e); } */ try { if (digestCalculator != null) { resultDigest = digestCalculator.getDigest(); } else { if (content != null) { content.write(new DigOutputStream(digest)); } else if (signedAttributeSet == null) { // TODO Get rid of this exception and just treat content==null as empty not missing? throw new CMSException("data not encapsulated in signature - use detached constructor."); } resultDigest = digest.digest(); } } catch (IOException e) { throw new CMSException("can't process mime object to create signature.", e); } // RFC 3852 11.1 Check the content-type attribute is correct { DERObject validContentType = getSingleValuedSignedAttribute(CMSAttributes.contentType, "content-type"); if (validContentType == null) { if (!isCounterSignature && signedAttributeSet != null) { throw new CMSException( "The content-type attribute type MUST be present whenever signed attributes are present in signed-data"); } } else { if (isCounterSignature) { throw new CMSException( "[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute"); } if (!(validContentType instanceof DERObjectIdentifier)) { throw new CMSException( "content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'"); } DERObjectIdentifier signedContentType = (DERObjectIdentifier) validContentType; if (!signedContentType.equals(contentType)) { throw new CMSException("content-type attribute value does not match eContentType"); } } } // RFC 3852 11.2 Check the message-digest attribute is correct { DERObject validMessageDigest = getSingleValuedSignedAttribute(CMSAttributes.messageDigest, "message-digest"); if (validMessageDigest == null) { if (signedAttributeSet != null) { throw new CMSException( "the message-digest signed attribute type MUST be present when there are any signed attributes present"); } } else { if (!(validMessageDigest instanceof ASN1OctetString)) { throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'"); } ASN1OctetString signedMessageDigest = (ASN1OctetString) validMessageDigest; if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets())) { throw new CMSSignerDigestMismatchException( "message-digest attribute value does not match calculated value"); } } } // RFC 3852 11.4 Validate countersignature attribute(s) { AttributeTable signedAttrTable = this.getSignedAttributes(); if (signedAttrTable != null && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0) { throw new CMSException("A countersignature attribute MUST NOT be a signed attribute"); } AttributeTable unsignedAttrTable = this.getUnsignedAttributes(); if (unsignedAttrTable != null) { ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature); for (int i = 0; i < csAttrs.size(); ++i) { Attribute csAttr = (Attribute) csAttrs.get(i); if (csAttr.getAttrValues().size() < 1) { throw new CMSException( "A countersignature attribute MUST contain at least one AttributeValue"); } // Note: We don't recursively validate the countersignature value } } } try { sig.initVerify(key); if (signedAttributeSet == null) { if (digestCalculator != null) { // need to decrypt signature and check message bytes return verifyDigest(resultDigest, key, this.getSignature(), sigProvider); } else if (content != null) { // TODO Use raw signature of the hash value instead content.write(new SigOutputStream(sig)); } } else { sig.update(this.getEncodedSignedAttributes()); } return sig.verify(this.getSignature()); } catch (InvalidKeyException e) { throw new CMSException("key not appropriate to signature in message.", e); } catch (IOException e) { throw new CMSException("can't process mime object to create signature.", e); } catch (SignatureException e) { throw new CMSException("invalid signature format in message: " + e.getMessage(), e); } }
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; } }