diff options
author | Andreas Kling <kling@serenityos.org> | 2021-01-12 12:17:30 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-12 12:17:46 +0100 |
commit | 13d7c09125f8eec703d0a43a9a87fc8aa08f7319 (patch) | |
tree | 70fd643c429cea5c1f9362c2674511d17a53f3b5 /Userland/Libraries/LibTLS | |
parent | dc28c07fa526841e05e16161c74a6c23984f1dd5 (diff) | |
download | serenity-13d7c09125f8eec703d0a43a9a87fc8aa08f7319.zip |
Libraries: Move to Userland/Libraries/
Diffstat (limited to 'Userland/Libraries/LibTLS')
-rw-r--r-- | Userland/Libraries/LibTLS/CMakeLists.txt | 11 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/Certificate.h | 97 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/ClientHandshake.cpp | 667 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/Exchange.cpp | 280 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/Handshake.cpp | 177 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/Record.cpp | 472 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/Socket.cpp | 260 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/TLSPacketBuilder.h | 120 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/TLSv12.cpp | 887 | ||||
-rw-r--r-- | Userland/Libraries/LibTLS/TLSv12.h | 509 |
10 files changed, 3480 insertions, 0 deletions
diff --git a/Userland/Libraries/LibTLS/CMakeLists.txt b/Userland/Libraries/LibTLS/CMakeLists.txt new file mode 100644 index 0000000000..e4194ac456 --- /dev/null +++ b/Userland/Libraries/LibTLS/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES + ClientHandshake.cpp + Exchange.cpp + Handshake.cpp + Record.cpp + Socket.cpp + TLSv12.cpp +) + +serenity_lib(LibTLS tls) +target_link_libraries(LibTLS LibCore LibCrypto) diff --git a/Userland/Libraries/LibTLS/Certificate.h b/Userland/Libraries/LibTLS/Certificate.h new file mode 100644 index 0000000000..22953092d4 --- /dev/null +++ b/Userland/Libraries/LibTLS/Certificate.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/ByteBuffer.h> +#include <AK/Forward.h> +#include <AK/Singleton.h> +#include <AK/Types.h> +#include <LibCrypto/BigInt/UnsignedBigInteger.h> +#include <LibCrypto/PK/RSA.h> + +namespace TLS { + +enum class CertificateKeyAlgorithm { + Unsupported = 0x00, + RSA_RSA = 0x01, + RSA_MD5 = 0x04, + RSA_SHA1 = 0x05, + RSA_SHA256 = 0x0b, + RSA_SHA512 = 0x0d, +}; + +struct Certificate { + u16 version; + CertificateKeyAlgorithm algorithm; + CertificateKeyAlgorithm key_algorithm; + CertificateKeyAlgorithm ec_algorithm; + ByteBuffer exponent; + Crypto::PK::RSAPublicKey<Crypto::UnsignedBigInteger> public_key; + Crypto::PK::RSAPrivateKey<Crypto::UnsignedBigInteger> private_key; + String issuer_country; + String issuer_state; + String issuer_location; + String issuer_entity; + String issuer_subject; + String issuer_unit; + String not_before; + String not_after; + String country; + String state; + String location; + String entity; + String subject; + String unit; + Vector<String> SAN; + u8* ocsp; + Crypto::UnsignedBigInteger serial_number; + ByteBuffer sign_key; + ByteBuffer fingerprint; + ByteBuffer der; + ByteBuffer data; + + bool is_valid() const; +}; + +class DefaultRootCACertificates { +public: + DefaultRootCACertificates(); + + const Vector<Certificate>& certificates() const { return m_ca_certificates; } + + static DefaultRootCACertificates& the() { return s_the; } + +private: + static AK::Singleton<DefaultRootCACertificates> s_the; + + Vector<Certificate> m_ca_certificates; +}; + +} + +using TLS::Certificate; +using TLS::DefaultRootCACertificates; diff --git a/Userland/Libraries/LibTLS/ClientHandshake.cpp b/Userland/Libraries/LibTLS/ClientHandshake.cpp new file mode 100644 index 0000000000..b1fbaa2391 --- /dev/null +++ b/Userland/Libraries/LibTLS/ClientHandshake.cpp @@ -0,0 +1,667 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/Endian.h> +#include <AK/Random.h> + +#include <LibCore/Timer.h> +#include <LibCrypto/ASN1/DER.h> +#include <LibCrypto/PK/Code/EMSA_PSS.h> +#include <LibTLS/TLSv12.h> + +namespace TLS { + +ssize_t TLSv12::handle_server_hello_done(ReadonlyBytes buffer) +{ + if (buffer.size() < 3) + return (i8)Error::NeedMoreData; + + size_t size = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2]; + + if (buffer.size() - 3 < size) + return (i8)Error::NeedMoreData; + + return size + 3; +} + +ssize_t TLSv12::handle_hello(ReadonlyBytes buffer, WritePacketStage& write_packets) +{ + write_packets = WritePacketStage::Initial; + if (m_context.connection_status != ConnectionStatus::Disconnected && m_context.connection_status != ConnectionStatus::Renegotiating) { + dbgln("unexpected hello message"); + return (i8)Error::UnexpectedMessage; + } + ssize_t res = 0; + size_t min_hello_size = 41; + + if (min_hello_size > buffer.size()) { + dbgln("need more data"); + return (i8)Error::NeedMoreData; + } + size_t following_bytes = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2]; + res += 3; + if (buffer.size() - res < following_bytes) { + dbg() << "not enough data after header: " << buffer.size() - res << " < " << following_bytes; + return (i8)Error::NeedMoreData; + } + + if (buffer.size() - res < 2) { + dbgln("not enough data for version"); + return (i8)Error::NeedMoreData; + } + auto version = (Version)AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res)); + + res += 2; + if (!supports_version(version)) + return (i8)Error::NotSafe; + + memcpy(m_context.remote_random, buffer.offset_pointer(res), sizeof(m_context.remote_random)); + res += sizeof(m_context.remote_random); + + u8 session_length = buffer[res++]; + if (buffer.size() - res < session_length) { + dbgln("not enough data for session id"); + return (i8)Error::NeedMoreData; + } + + if (session_length && session_length <= 32) { + memcpy(m_context.session_id, buffer.offset_pointer(res), session_length); + m_context.session_id_size = session_length; +#ifdef TLS_DEBUG + dbgln("Remote session ID:"); + print_buffer(ReadonlyBytes { m_context.session_id, session_length }); +#endif + } else { + m_context.session_id_size = 0; + } + res += session_length; + + if (buffer.size() - res < 2) { + dbgln("not enough data for cipher suite listing"); + return (i8)Error::NeedMoreData; + } + auto cipher = (CipherSuite)AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res)); + res += 2; + if (!supports_cipher(cipher)) { + m_context.cipher = CipherSuite::Invalid; + dbgln("No supported cipher could be agreed upon"); + return (i8)Error::NoCommonCipher; + } + m_context.cipher = cipher; +#ifdef TLS_DEBUG + dbg() << "Cipher: " << (u16)cipher; +#endif + + // The handshake hash function is _always_ SHA256 + m_context.handshake_hash.initialize(Crypto::Hash::HashKind::SHA256); + + if (buffer.size() - res < 1) { + dbgln("not enough data for compression spec"); + return (i8)Error::NeedMoreData; + } + u8 compression = buffer[res++]; + if (compression != 0) { + dbgln("Server told us to compress, we will not!"); + return (i8)Error::CompressionNotSupported; + } + + if (res > 0) { + if (m_context.connection_status != ConnectionStatus::Renegotiating) + m_context.connection_status = ConnectionStatus::Negotiating; + if (m_context.is_server) { + dbgln("unsupported: server mode"); + write_packets = WritePacketStage::ServerHandshake; + } + } + + if (res > 2) { + res += 2; + } + + while ((ssize_t)buffer.size() - res >= 4) { + auto extension_type = (HandshakeExtension)AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res)); + res += 2; + u16 extension_length = AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res)); + res += 2; + +#ifdef TLS_DEBUG + dbg() << "extension " << (u16)extension_type << " with length " << extension_length; +#endif + if (extension_length) { + if (buffer.size() - res < extension_length) { + dbgln("not enough data for extension"); + return (i8)Error::NeedMoreData; + } + + // SNI + if (extension_type == HandshakeExtension::ServerName) { + u16 sni_host_length = AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res + 3)); + if (buffer.size() - res - 5 < sni_host_length) { + dbg() << "Not enough data for sni " << (buffer.size() - res - 5) << " < " << sni_host_length; + return (i8)Error::NeedMoreData; + } + + if (sni_host_length) { + m_context.SNI = String { (const char*)buffer.offset_pointer(res + 5), sni_host_length }; + dbg() << "server name indicator: " << m_context.SNI; + } + } else if (extension_type == HandshakeExtension::ApplicationLayerProtocolNegotiation && m_context.alpn.size()) { + if (buffer.size() - res > 2) { + auto alpn_length = AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(res)); + if (alpn_length && alpn_length <= extension_length - 2) { + const u8* alpn = buffer.offset_pointer(res + 2); + size_t alpn_position = 0; + while (alpn_position < alpn_length) { + u8 alpn_size = alpn[alpn_position++]; + if (alpn_size + alpn_position >= extension_length) + break; + String alpn_str { (const char*)alpn + alpn_position, alpn_length }; + if (alpn_size && m_context.alpn.contains_slow(alpn_str)) { + m_context.negotiated_alpn = alpn_str; + dbg() << "negotiated alpn: " << alpn_str; + break; + } + alpn_position += alpn_length; + if (!m_context.is_server) // server hello must contain one ALPN + break; + } + } + } + } else if (extension_type == HandshakeExtension::SignatureAlgorithms) { + dbgln("supported signatures: "); + print_buffer(buffer.slice(res, extension_length)); + // FIXME: what are we supposed to do here? + } + res += extension_length; + } + } + + return res; +} + +ssize_t TLSv12::handle_finished(ReadonlyBytes buffer, WritePacketStage& write_packets) +{ + if (m_context.connection_status < ConnectionStatus::KeyExchange || m_context.connection_status == ConnectionStatus::Established) { + dbgln("unexpected finished message"); + return (i8)Error::UnexpectedMessage; + } + + write_packets = WritePacketStage::Initial; + + if (buffer.size() < 3) { + return (i8)Error::NeedMoreData; + } + + size_t index = 3; + + u32 size = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2]; + + if (size < 12) { +#ifdef TLS_DEBUG + dbg() << "finished packet smaller than minimum size: " << size; +#endif + return (i8)Error::BrokenPacket; + } + + if (size < buffer.size() - index) { +#ifdef TLS_DEBUG + dbg() << "not enough data after length: " << size << " > " << buffer.size() - index; +#endif + return (i8)Error::NeedMoreData; + } + +// TODO: Compare Hashes +#ifdef TLS_DEBUG + dbgln("FIXME: handle_finished :: Check message validity"); +#endif + m_context.connection_status = ConnectionStatus::Established; + + if (m_handshake_timeout_timer) { + // Disable the handshake timeout timer as handshake has been established. + m_handshake_timeout_timer->stop(); + m_handshake_timeout_timer->remove_from_parent(); + m_handshake_timeout_timer = nullptr; + } + + if (on_tls_ready_to_write) + on_tls_ready_to_write(*this); + + return index + size; +} + +void TLSv12::build_random(PacketBuilder& builder) +{ + u8 random_bytes[48]; + size_t bytes = 48; + + AK::fill_with_random(random_bytes, bytes); + + // remove zeros from the random bytes + for (size_t i = 0; i < bytes; ++i) { + if (!random_bytes[i]) + random_bytes[i--] = AK::get_random<u8>(); + } + + if (m_context.is_server) { + dbgln("Server mode not supported"); + return; + } else { + *(u16*)random_bytes = AK::convert_between_host_and_network_endian((u16)Version::V12); + } + + m_context.premaster_key = ByteBuffer::copy(random_bytes, bytes); + + const auto& certificate_option = verify_chain_and_get_matching_certificate(m_context.SNI); // if the SNI is empty, we'll make a special case and match *a* leaf certificate. + if (!certificate_option.has_value()) { + dbgln("certificate verification failed :("); + alert(AlertLevel::Critical, AlertDescription::BadCertificate); + return; + } + + auto& certificate = m_context.certificates[certificate_option.value()]; +#ifdef TLS_DEBUG + dbgln("PreMaster secret"); + print_buffer(m_context.premaster_key); +#endif + + Crypto::PK::RSA_PKCS1_EME rsa(certificate.public_key.modulus(), 0, certificate.public_key.public_exponent()); + + u8 out[rsa.output_size()]; + auto outbuf = Bytes { out, rsa.output_size() }; + rsa.encrypt(m_context.premaster_key, outbuf); + +#ifdef TLS_DEBUG + dbgln("Encrypted: "); + print_buffer(outbuf); +#endif + + if (!compute_master_secret(bytes)) { + dbgln("oh noes we could not derive a master key :("); + return; + } + + builder.append_u24(outbuf.size() + 2); + builder.append((u16)outbuf.size()); + builder.append(outbuf); +} + +ssize_t TLSv12::handle_payload(ReadonlyBytes vbuffer) +{ + if (m_context.connection_status == ConnectionStatus::Established) { +#ifdef TLS_DEBUG + dbgln("Renegotiation attempt ignored"); +#endif + // FIXME: We should properly say "NoRenegotiation", but that causes a handshake failure + // so we just roll with it and pretend that we _did_ renegotiate + // This will cause issues when we decide to have long-lasting connections, but + // we do not have those at the moment :^) + return 1; + } + auto buffer = vbuffer; + auto buffer_length = buffer.size(); + auto original_length = buffer_length; + while (buffer_length >= 4 && !m_context.critical_error) { + ssize_t payload_res = 0; + if (buffer_length < 1) + return (i8)Error::NeedMoreData; + auto type = buffer[0]; + auto write_packets { WritePacketStage::Initial }; + size_t payload_size = buffer[1] * 0x10000 + buffer[2] * 0x100 + buffer[3] + 3; +#ifdef TLS_DEBUG + dbg() << "payload size: " << payload_size << " buffer length: " << buffer_length; +#endif + if (payload_size + 1 > buffer_length) + return (i8)Error::NeedMoreData; + + switch (type) { + case HelloRequest: + if (m_context.handshake_messages[0] >= 1) { + dbgln("unexpected hello request message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[0]; + dbgln("hello request (renegotiation?)"); + if (m_context.connection_status == ConnectionStatus::Established) { + // renegotiation + payload_res = (i8)Error::NoRenegotiation; + } else { + // :shrug: + payload_res = (i8)Error::UnexpectedMessage; + } + break; + case ClientHello: + // FIXME: We only support client mode right now + if (m_context.is_server) { + ASSERT_NOT_REACHED(); + } + payload_res = (i8)Error::UnexpectedMessage; + break; + case ServerHello: + if (m_context.handshake_messages[2] >= 1) { + dbgln("unexpected server hello message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[2]; +#ifdef TLS_DEBUG + dbgln("server hello"); +#endif + if (m_context.is_server) { + dbgln("unsupported: server mode"); + ASSERT_NOT_REACHED(); + } else { + payload_res = handle_hello(buffer.slice(1, payload_size), write_packets); + } + break; + case HelloVerifyRequest: + dbgln("unsupported: DTLS"); + payload_res = (i8)Error::UnexpectedMessage; + break; + case CertificateMessage: + if (m_context.handshake_messages[4] >= 1) { + dbgln("unexpected certificate message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[4]; +#ifdef TLS_DEBUG + dbgln("certificate"); +#endif + if (m_context.connection_status == ConnectionStatus::Negotiating) { + if (m_context.is_server) { + dbgln("unsupported: server mode"); + ASSERT_NOT_REACHED(); + } + payload_res = handle_certificate(buffer.slice(1, payload_size)); + if (m_context.certificates.size()) { + auto it = m_context.certificates.find_if([](const auto& cert) { return cert.is_valid(); }); + + if (it.is_end()) { + // no valid certificates + dbgln("No valid certificates found"); + payload_res = (i8)Error::BadCertificate; + m_context.critical_error = payload_res; + break; + } + + // swap the first certificate with the valid one + if (it.index() != 0) + swap(m_context.certificates[0], m_context.certificates[it.index()]); + } + } else { + payload_res = (i8)Error::UnexpectedMessage; + } + break; + case ServerKeyExchange: + if (m_context.handshake_messages[5] >= 1) { + dbgln("unexpected server key exchange message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[5]; +#ifdef TLS_DEBUG + dbgln("server key exchange"); +#endif + if (m_context.is_server) { + dbgln("unsupported: server mode"); + ASSERT_NOT_REACHED(); + } else { + payload_res = handle_server_key_exchange(buffer.slice(1, payload_size)); + } + break; + case CertificateRequest: + if (m_context.handshake_messages[6] >= 1) { + dbgln("unexpected certificate request message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[6]; + if (m_context.is_server) { + dbgln("invalid request"); + dbgln("unsupported: server mode"); + ASSERT_NOT_REACHED(); + } else { + // we do not support "certificate request" + dbgln("certificate request"); + if (on_tls_certificate_request) + on_tls_certificate_request(*this); + m_context.client_verified = VerificationNeeded; + } + break; + case ServerHelloDone: + if (m_context.handshake_messages[7] >= 1) { + dbgln("unexpected server hello done message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[7]; +#ifdef TLS_DEBUG + dbgln("server hello done"); +#endif + if (m_context.is_server) { + dbgln("unsupported: server mode"); + ASSERT_NOT_REACHED(); + } else { + payload_res = handle_server_hello_done(buffer.slice(1, payload_size)); + if (payload_res > 0) + write_packets = WritePacketStage::ClientHandshake; + } + break; + case CertificateVerify: + if (m_context.handshake_messages[8] >= 1) { + dbgln("unexpected certificate verify message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[8]; +#ifdef TLS_DEBUG + dbgln("certificate verify"); +#endif + if (m_context.connection_status == ConnectionStatus::KeyExchange) { + payload_res = handle_verify(buffer.slice(1, payload_size)); + } else { + payload_res = (i8)Error::UnexpectedMessage; + } + break; + case ClientKeyExchange: + if (m_context.handshake_messages[9] >= 1) { + dbgln("unexpected client key exchange message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[9]; +#ifdef TLS_DEBUG + dbgln("client key exchange"); +#endif + if (m_context.is_server) { + dbgln("unsupported: server mode"); + ASSERT_NOT_REACHED(); + } else { + payload_res = (i8)Error::UnexpectedMessage; + } + break; + case Finished: + if (m_context.cached_handshake) { + m_context.cached_handshake.clear(); + } + if (m_context.handshake_messages[10] >= 1) { + dbgln("unexpected finished message"); + payload_res = (i8)Error::UnexpectedMessage; + break; + } + ++m_context.handshake_messages[10]; +#ifdef TLS_DEBUG + dbgln("finished"); +#endif + payload_res = handle_finished(buffer.slice(1, payload_size), write_packets); + if (payload_res > 0) { + memset(m_context.handshake_messages, 0, sizeof(m_context.handshake_messages)); + } + break; + default: + dbg() << "message type not understood: " << type; + return (i8)Error::NotUnderstood; + } + + if (type != HelloRequest) { + update_hash(buffer.slice(0, payload_size + 1)); + } + + // if something went wrong, send an alert about it + if (payload_res < 0) { + switch ((Error)payload_res) { + case Error::UnexpectedMessage: { + auto packet = build_alert(true, (u8)AlertDescription::UnexpectedMessage); + write_packet(packet); + break; + } + case Error::CompressionNotSupported: { + auto packet = build_alert(true, (u8)AlertDescription::DecompressionFailure); + write_packet(packet); + break; + } + case Error::BrokenPacket: { + auto packet = build_alert(true, (u8)AlertDescription::DecodeError); + write_packet(packet); + break; + } + case Error::NotVerified: { + auto packet = build_alert(true, (u8)AlertDescription::BadRecordMAC); + write_packet(packet); + break; + } + case Error::BadCertificate: { + auto packet = build_alert(true, (u8)AlertDescription::BadCertificate); + write_packet(packet); + break; + } + case Error::UnsupportedCertificate: { + auto packet = build_alert(true, (u8)AlertDescription::UnsupportedCertificate); + write_packet(packet); + break; + } + case Error::NoCommonCipher: { + auto packet = build_alert(true, (u8)AlertDescription::InsufficientSecurity); + write_packet(packet); + break; + } + case Error::NotUnderstood: { + auto packet = build_alert(true, (u8)AlertDescription::InternalError); + write_packet(packet); + break; + } + case Error::NoRenegotiation: { + auto packet = build_alert(true, (u8)AlertDescription::NoRenegotiation); + write_packet(packet); + break; + } + case Error::DecryptionFailed: { + auto packet = build_alert(true, (u8)AlertDescription::DecryptionFailed); + write_packet(packet); + break; + } + case Error::NeedMoreData: + // Ignore this, as it's not an "error" + break; + default: + dbg() << "Unknown TLS::Error with value " << payload_res; + ASSERT_NOT_REACHED(); + break; + } + if (payload_res < 0) + return payload_res; + } + switch (write_packets) { + case WritePacketStage::Initial: + // nothing to write + break; + case WritePacketStage::ClientHandshake: + if (m_context.client_verified == VerificationNeeded) { +#ifdef TLS_DEBUG + dbgln("> Client Certificate"); +#endif + auto packet = build_certificate(); + write_packet(packet); + m_context.client_verified = Verified; + } + { +#ifdef TLS_DEBUG + dbgln("> Key exchange"); +#endif + auto packet = build_client_key_exchange(); + write_packet(packet); + } + { +#ifdef TLS_DEBUG + dbgln("> change cipher spec"); +#endif + auto packet = build_change_cipher_spec(); + write_packet(packet); + } + m_context.cipher_spec_set = 1; + m_context.local_sequence_number = 0; + { +#ifdef TLS_DEBUG + dbgln("> client finished"); +#endif + auto packet = build_finished(); + write_packet(packet); + } + m_context.cipher_spec_set = 0; + break; + case WritePacketStage::ServerHandshake: + // server handshake + dbgln("UNSUPPORTED: Server mode"); + ASSERT_NOT_REACHED(); + break; + case WritePacketStage::Finished: + // finished + { +#ifdef TLS_DEBUG + dbgln("> change cipher spec"); +#endif + auto packet = build_change_cipher_spec(); + write_packet(packet); + } + { +#ifdef TLS_DEBUG + dbgln("> client finished"); +#endif + auto packet = build_finished(); + write_packet(packet); + } + m_context.connection_status = ConnectionStatus::Established; + break; + } + payload_size++; + buffer_length -= payload_size; + buffer = buffer.slice(payload_size, buffer_length); + } + return original_length; +} + +} diff --git a/Userland/Libraries/LibTLS/Exchange.cpp b/Userland/Libraries/LibTLS/Exchange.cpp new file mode 100644 index 0000000000..0765bc5e9d --- /dev/null +++ b/Userland/Libraries/LibTLS/Exchange.cpp @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibCrypto/ASN1/DER.h> +#include <LibCrypto/PK/Code/EMSA_PSS.h> +#include <LibTLS/TLSv12.h> + +namespace TLS { + +bool TLSv12::expand_key() +{ + u8 key[192]; // soooooooo many constants + auto key_buffer = Bytes { key, sizeof(key) }; + + auto is_aead = this->is_aead(); + + if (m_context.master_key.size() == 0) { + dbgln("expand_key() with empty master key"); + return false; + } + + auto key_size = key_length(); + auto mac_size = mac_length(); + auto iv_size = iv_length(); + + pseudorandom_function( + key_buffer, + m_context.master_key, + (const u8*)"key expansion", 13, + ReadonlyBytes { m_context.remote_random, sizeof(m_context.remote_random) }, + ReadonlyBytes { m_context.local_random, sizeof(m_context.local_random) }); + + size_t offset = 0; + if (is_aead) { + iv_size = 4; // Explicit IV size. + } else { + memcpy(m_context.crypto.local_mac, key + offset, mac_size); + offset += mac_size; + memcpy(m_context.crypto.remote_mac, key + offset, mac_size); + offset += mac_size; + } + + auto client_key = key + offset; + offset += key_size; + auto server_key = key + offset; + offset += key_size; + auto client_iv = key + offset; + offset += iv_size; + auto server_iv = key + offset; + offset += iv_size; + +#ifdef TLS_DEBUG + dbgln("client key"); + print_buffer(client_key, key_size); + dbgln("server key"); + print_buffer(server_key, key_size); + dbgln("client iv"); + print_buffer(client_iv, iv_size); + dbgln("server iv"); + print_buffer(server_iv, iv_size); + if (!is_aead) { + dbgln("client mac key"); + print_buffer(m_context.crypto.local_mac, mac_size); + dbgln("server mac key"); + print_buffer(m_context.crypto.remote_mac, mac_size); + } +#endif + + if (is_aead) { + memcpy(m_context.crypto.local_aead_iv, client_iv, iv_size); + memcpy(m_context.crypto.remote_aead_iv, server_iv, iv_size); + + m_aes_local.gcm = make<Crypto::Cipher::AESCipher::GCMMode>(ReadonlyBytes { client_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246); + m_aes_remote.gcm = make<Crypto::Cipher::AESCipher::GCMMode>(ReadonlyBytes { server_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246); + } else { + memcpy(m_context.crypto.local_iv, client_iv, iv_size); + memcpy(m_context.crypto.remote_iv, server_iv, iv_size); + + m_aes_local.cbc = make<Crypto::Cipher::AESCipher::CBCMode>(ReadonlyBytes { client_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246); + m_aes_remote.cbc = make<Crypto::Cipher::AESCipher::CBCMode>(ReadonlyBytes { server_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246); + } + + m_context.crypto.created = 1; + + return true; +} + +void TLSv12::pseudorandom_function(Bytes output, ReadonlyBytes secret, const u8* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b) +{ + if (!secret.size()) { + dbgln("null secret"); + return; + } + + // RFC 5246: "In this section, we define one PRF, based on HMAC. This PRF with the + // SHA-256 hash function is used for all cipher suites defined in this + // document and in TLS documents published prior to this document when + // TLS 1.2 is negotiated." + // Apparently this PRF _always_ uses SHA256 + Crypto::Authentication::HMAC<Crypto::Hash::SHA256> hmac(secret); + + auto l_seed_size = label_length + seed.size() + seed_b.size(); + u8 l_seed[l_seed_size]; + auto label_seed_buffer = Bytes { l_seed, l_seed_size }; + label_seed_buffer.overwrite(0, label, label_length); + label_seed_buffer.overwrite(label_length, seed.data(), seed.size()); + label_seed_buffer.overwrite(label_length + seed.size(), seed_b.data(), seed_b.size()); + + auto digest_size = hmac.digest_size(); + + u8 digest[digest_size]; + + auto digest_0 = Bytes { digest, digest_size }; + + digest_0.overwrite(0, hmac.process(label_seed_buffer).immutable_data(), digest_size); + + size_t index = 0; + while (index < output.size()) { + hmac.update(digest_0); + hmac.update(label_seed_buffer); + auto digest_1 = hmac.digest(); + + auto copy_size = min(digest_size, output.size() - index); + + output.overwrite(index, digest_1.immutable_data(), copy_size); + index += copy_size; + + digest_0.overwrite(0, hmac.process(digest_0).immutable_data(), digest_size); + } +} + +bool TLSv12::compute_master_secret(size_t length) +{ + if (m_context.premaster_key.size() == 0 || length < 48) { + dbgln("there's no way I can make a master secret like this"); + dbg() << "I'd like to talk to your manager about this length of " << length; + return false; + } + + m_context.master_key.clear(); + m_context.master_key.grow(length); + + pseudorandom_function( + m_context.master_key, + m_context.premaster_key, + (const u8*)"master secret", 13, + ReadonlyBytes { m_context.local_random, sizeof(m_context.local_random) }, + ReadonlyBytes { m_context.remote_random, sizeof(m_context.remote_random) }); + + m_context.premaster_key.clear(); +#ifdef TLS_DEBUG + dbgln("master key:"); + print_buffer(m_context.master_key); +#endif + expand_key(); + return true; +} + +ByteBuffer TLSv12::build_certificate() +{ + PacketBuilder builder { MessageType::Handshake, m_context.version }; + + Vector<const Certificate*> certificates; + Vector<Certificate>* local_certificates = nullptr; + + if (m_context.is_server) { + dbgln("Unsupported: Server mode"); + ASSERT_NOT_REACHED(); + } else { + local_certificates = &m_context.client_certificates; + } + + constexpr size_t der_length_delta = 3; + constexpr size_t certificate_vector_header_size = 3; + + size_t total_certificate_size = 0; + + for (size_t i = 0; i < local_certificates->size(); ++i) { + auto& certificate = local_certificates->at(i); + if (!certificate.der.is_empty()) { + total_certificate_size += certificate.der.size() + der_length_delta; + + // FIXME: Check for and respond with only the requested certificate types. + if (true) { + certificates.append(&certificate); + } + } + } + + builder.append((u8)HandshakeType::CertificateMessage); + + if (!total_certificate_size) { +#ifdef TLS_DEBUG + dbgln("No certificates, sending empty certificate message"); +#endif + builder.append_u24(certificate_vector_header_size); + builder.append_u24(total_certificate_size); + } else { + builder.append_u24(total_certificate_size + certificate_vector_header_size); // 3 bytes for header + builder.append_u24(total_certificate_size); + + for (auto& certificate : certificates) { + if (!certificate->der.is_empty()) { + builder.append_u24(certificate->der.size()); + builder.append(certificate->der.bytes()); + } + } + } + auto packet = builder.build(); + update_packet(packet); + return packet; +} + +ByteBuffer TLSv12::build_change_cipher_spec() +{ + PacketBuilder builder { MessageType::ChangeCipher, m_context.version, 64 }; + builder.append((u8)1); + auto packet = builder.build(); + update_packet(packet); + m_context.local_sequence_number = 0; + return packet; +} + +ByteBuffer TLSv12::build_server_key_exchange() +{ + dbgln("FIXME: build_server_key_exchange"); + return {}; +} + +ByteBuffer TLSv12::build_client_key_exchange() +{ + PacketBuilder builder { MessageType::Handshake, m_context.version }; + builder.append((u8)HandshakeType::ClientKeyExchange); + build_random(builder); + + m_context.connection_status = ConnectionStatus::KeyExchange; + + auto packet = builder.build(); + + update_packet(packet); + + return packet; +} + +ssize_t TLSv12::handle_server_key_exchange(ReadonlyBytes) +{ + dbgln("FIXME: parse_server_key_exchange"); + return 0; +} + +ssize_t TLSv12::handle_verify(ReadonlyBytes) +{ + dbgln("FIXME: parse_verify"); + return 0; +} + +} diff --git a/Userland/Libraries/LibTLS/Handshake.cpp b/Userland/Libraries/LibTLS/Handshake.cpp new file mode 100644 index 0000000000..1fa0f01fca --- /dev/null +++ b/Userland/Libraries/LibTLS/Handshake.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/Random.h> +#include <LibCrypto/ASN1/DER.h> +#include <LibCrypto/PK/Code/EMSA_PSS.h> +#include <LibTLS/TLSv12.h> + +namespace TLS { + +ByteBuffer TLSv12::build_hello() +{ + AK::fill_with_random(&m_context.local_random, 32); + + auto packet_version = (u16)m_context.version; + auto version = (u16)m_context.version; + PacketBuilder builder { MessageType::Handshake, packet_version }; + + builder.append((u8)ClientHello); + + // hello length (for later) + u8 dummy[3] = {}; + builder.append(dummy, 3); + + auto start_length = builder.length(); + + builder.append(version); + builder.append(m_context.local_random, sizeof(m_context.local_random)); + + builder.append(m_context.session_id_size); + if (m_context.session_id_size) + builder.append(m_context.session_id, m_context.session_id_size); + + size_t extension_length = 0; + size_t alpn_length = 0; + size_t alpn_negotiated_length = 0; + + // ALPN + if (!m_context.negotiated_alpn.is_null()) { + alpn_negotiated_length = m_context.negotiated_alpn.length(); + alpn_length = alpn_negotiated_length + 1; + extension_length += alpn_length + 6; + } else if (m_context.alpn.size()) { + for (auto& alpn : m_context.alpn) { + size_t length = alpn.length(); + alpn_length += length + 1; + } + if (alpn_length) + extension_length += alpn_length + 6; + } + + // Ciphers + builder.append((u16)(5 * sizeof(u16))); + builder.append((u16)CipherSuite::RSA_WITH_AES_128_CBC_SHA256); + builder.append((u16)CipherSuite::RSA_WITH_AES_256_CBC_SHA256); + builder.append((u16)CipherSuite::RSA_WITH_AES_128_CBC_SHA); + builder.append((u16)CipherSuite::RSA_WITH_AES_256_CBC_SHA); + builder.append((u16)CipherSuite::RSA_WITH_AES_128_GCM_SHA256); + + // we don't like compression + builder.append((u8)1); + builder.append((u8)0); + + // set SNI if we have one + auto sni_length = 0; + if (!m_context.SNI.is_null()) + sni_length = m_context.SNI.length(); + + if (sni_length) + extension_length += sni_length + 9; + + builder.append((u16)extension_length); + + if (sni_length) { + // SNI extension + builder.append((u16)HandshakeExtension::ServerName); + // extension length + builder.append((u16)(sni_length + 5)); + // SNI length + builder.append((u16)(sni_length + 3)); + // SNI type + builder.append((u8)0); + // SNI host length + value + builder.append((u16)sni_length); + builder.append((const u8*)m_context.SNI.characters(), sni_length); + } + + if (alpn_length) { + // TODO + ASSERT_NOT_REACHED(); + } + + // set the "length" field of the packet + size_t remaining = builder.length() - start_length; + size_t payload_position = 6; + builder.set(payload_position, remaining / 0x10000); + remaining %= 0x10000; + builder.set(payload_position + 1, remaining / 0x100); + remaining %= 0x100; + builder.set(payload_position + 2, remaining); + + auto packet = builder.build(); + update_packet(packet); + + return packet; +} + +ByteBuffer TLSv12::build_alert(bool critical, u8 code) +{ + PacketBuilder builder(MessageType::Alert, (u16)m_context.version); + builder.append((u8)(critical ? AlertLevel::Critical : AlertLevel::Warning)); + builder.append(code); + + if (critical) + m_context.critical_error = code; + + auto packet = builder.build(); + update_packet(packet); + + return packet; +} + +ByteBuffer TLSv12::build_finished() +{ + PacketBuilder builder { MessageType::Handshake, m_context.version, 12 + 64 }; + builder.append((u8)HandshakeType::Finished); + + u32 out_size = 12; + + builder.append_u24(out_size); + + u8 out[out_size]; + auto outbuffer = Bytes { out, out_size }; + auto dummy = ByteBuffer::create_zeroed(0); + + auto digest = m_context.handshake_hash.digest(); + auto hashbuf = ReadonlyBytes { digest.immutable_data(), m_context.handshake_hash.digest_size() }; + pseudorandom_function(outbuffer, m_context.master_key, (const u8*)"client finished", 15, hashbuf, dummy); + + builder.append(outbuffer); + auto packet = builder.build(); + update_packet(packet); + + return packet; +} + +void TLSv12::alert(AlertLevel level, AlertDescription code) +{ + auto the_alert = build_alert(level == AlertLevel::Critical, (u8)code); + write_packet(the_alert); + flush(); +} + +} diff --git a/Userland/Libraries/LibTLS/Record.cpp b/Userland/Libraries/LibTLS/Record.cpp new file mode 100644 index 0000000000..1f2e875926 --- /dev/null +++ b/Userland/Libraries/LibTLS/Record.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/Endian.h> + +#include <AK/MemoryStream.h> +#include <LibCore/Timer.h> +#include <LibCrypto/ASN1/DER.h> +#include <LibCrypto/PK/Code/EMSA_PSS.h> +#include <LibTLS/TLSv12.h> + +namespace TLS { + +void TLSv12::write_packet(ByteBuffer& packet) +{ + m_context.tls_buffer.append(packet.data(), packet.size()); + if (m_context.connection_status > ConnectionStatus::Disconnected) { + if (!m_has_scheduled_write_flush) { +#ifdef TLS_DEBUG + dbg() << "Scheduling write of " << m_context.tls_buffer.size(); +#endif + deferred_invoke([this](auto&) { write_into_socket(); }); + m_has_scheduled_write_flush = true; + } else { + // multiple packet are available, let's flush some out +#ifdef TLS_DEBUG + dbg() << "Flushing scheduled write of " << m_context.tls_buffer.size(); +#endif + write_into_socket(); + // the deferred invoke is still in place + m_has_scheduled_write_flush = true; + } + } +} + +void TLSv12::update_packet(ByteBuffer& packet) +{ + u32 header_size = 5; + *(u16*)packet.offset_pointer(3) = AK::convert_between_host_and_network_endian((u16)(packet.size() - header_size)); + + if (packet[0] != (u8)MessageType::ChangeCipher) { + if (packet[0] == (u8)MessageType::Handshake && packet.size() > header_size) { + u8 handshake_type = packet[header_size]; + if (handshake_type != HandshakeType::HelloRequest && handshake_type != HandshakeType::HelloVerifyRequest) { + update_hash(packet.bytes().slice(header_size, packet.size() - header_size)); + } + } + if (m_context.cipher_spec_set && m_context.crypto.created) { + size_t length = packet.size() - header_size; + size_t block_size, padding, mac_size; + + if (!is_aead()) { + block_size = m_aes_local.cbc->cipher().block_size(); + // If the length is already a multiple a block_size, + // an entire block of padding is added. + // In short, we _never_ have no padding. + mac_size = mac_length(); + length += mac_size; + padding = block_size - length % block_size; + length += padding; + } else { + block_size = m_aes_local.gcm->cipher().block_size(); + padding = 0; + mac_size = 0; // AEAD provides its own authentication scheme. + } + + if (m_context.crypto.created == 1) { + // `buffer' will continue to be encrypted + auto buffer = ByteBuffer::create_uninitialized(length); + size_t buffer_position = 0; + auto iv_size = iv_length(); + + // copy the packet, sans the header + buffer.overwrite(buffer_position, packet.offset_pointer(header_size), packet.size() - header_size); + buffer_position += packet.size() - header_size; + + ByteBuffer ct; + + if (is_aead()) { + // We need enough space for a header, the data, a tag, and the IV + ct = ByteBuffer::create_uninitialized(length + header_size + iv_size + 16); + + // copy the header over + ct.overwrite(0, packet.data(), header_size - 2); + + // AEAD AAD (13) + // Seq. no (8) + // content type (1) + // version (2) + // length (2) + u8 aad[13]; + Bytes aad_bytes { aad, 13 }; + OutputMemoryStream aad_stream { aad_bytes }; + + u64 seq_no = AK::convert_between_host_and_network_endian(m_context.local_sequence_number); + u16 len = AK::convert_between_host_and_network_endian((u16)(packet.size() - header_size)); + + aad_stream.write({ &seq_no, sizeof(seq_no) }); + aad_stream.write(packet.bytes().slice(0, 3)); // content-type + version + aad_stream.write({ &len, sizeof(len) }); // length + ASSERT(aad_stream.is_end()); + + // AEAD IV (12) + // IV (4) + // (Nonce) (8) + // -- Our GCM impl takes 16 bytes + // zero (4) + u8 iv[16]; + Bytes iv_bytes { iv, 16 }; + Bytes { m_context.crypto.local_aead_iv, 4 }.copy_to(iv_bytes); + AK::fill_with_random(iv_bytes.offset(4), 8); + memset(iv_bytes.offset(12), 0, 4); + + // write the random part of the iv out + iv_bytes.slice(4, 8).copy_to(ct.bytes().slice(header_size)); + + // Write the encrypted data and the tag + m_aes_local.gcm->encrypt( + packet.bytes().slice(header_size, length), + ct.bytes().slice(header_size + 8, length), + iv_bytes, + aad_bytes, + ct.bytes().slice(header_size + 8 + length, 16)); + + ASSERT(header_size + 8 + length + 16 == ct.size()); + + } else { + // We need enough space for a header, iv_length bytes of IV and whatever the packet contains + ct = ByteBuffer::create_uninitialized(length + header_size + iv_size); + + // copy the header over + ct.overwrite(0, packet.data(), header_size - 2); + + // get the appropricate HMAC value for the entire packet + auto mac = hmac_message(packet, {}, mac_size, true); + + // write the MAC + buffer.overwrite(buffer_position, mac.data(), mac.size()); + buffer_position += mac.size(); + + // Apply the padding (a packet MUST always be padded) + memset(buffer.offset_pointer(buffer_position), padding - 1, padding); + buffer_position += padding; + + ASSERT(buffer_position == buffer.size()); + + auto iv = ByteBuffer::create_uninitialized(iv_size); + AK::fill_with_random(iv.data(), iv.size()); + + // write it into the ciphertext portion of the message + ct.overwrite(header_size, iv.data(), iv.size()); + + ASSERT(header_size + iv_size + length == ct.size()); + ASSERT(length % block_size == 0); + + // get a block to encrypt into + auto view = ct.bytes().slice(header_size + iv_size, length); + m_aes_local.cbc->encrypt(buffer, view, iv); + } + + // store the correct ciphertext length into the packet + u16 ct_length = (u16)ct.size() - header_size; + + *(u16*)ct.offset_pointer(header_size - 2) = AK::convert_between_host_and_network_endian(ct_length); + + // replace the packet with the ciphertext + packet = ct; + } + } + } + ++m_context.local_sequence_number; +} + +void TLSv12::update_hash(ReadonlyBytes message) +{ + m_context.handshake_hash.update(message); +} + +ByteBuffer TLSv12::hmac_message(const ReadonlyBytes& buf, const Optional<ReadonlyBytes> buf2, size_t mac_length, bool local) +{ + u64 sequence_number = AK::convert_between_host_and_network_endian(local ? m_context.local_sequence_number : m_context.remote_sequence_number); + ensure_hmac(mac_length, local); + auto& hmac = local ? *m_hmac_local : *m_hmac_remote; +#ifdef TLS_DEBUG + dbgln("========================= PACKET DATA =========================="); + print_buffer((const u8*)&sequence_number, sizeof(u64)); + print_buffer(buf.data(), buf.size()); + if (buf2.has_value()) + print_buffer(buf2.value().data(), buf2.value().size()); + dbgln("========================= PACKET DATA =========================="); +#endif + hmac.update((const u8*)&sequence_number, sizeof(u64)); + hmac.update(buf); + if (buf2.has_value() && buf2.value().size()) { + hmac.update(buf2.value()); + } + auto digest = hmac.digest(); + auto mac = ByteBuffer::copy(digest.immutable_data(), digest.data_length()); +#ifdef TLS_DEBUG + dbg() << "HMAC of the block for sequence number " << sequence_number; + print_buffer(mac); +#endif + return mac; +} + +ssize_t TLSv12::handle_message(ReadonlyBytes buffer) +{ + auto res { 5ll }; + size_t header_size = res; + ssize_t payload_res = 0; + +#ifdef TLS_DEBUG + dbg() << "buffer size: " << buffer.size(); +#endif + if (buffer.size() < 5) { + return (i8)Error::NeedMoreData; + } + + auto type = (MessageType)buffer[0]; + size_t buffer_position { 1 }; + + // FIXME: Read the version and verify it +#ifdef TLS_DEBUG + auto version = (Version) * (const u16*)buffer.offset_pointer(buffer_position); + dbg() << "type: " << (u8)type << " version: " << (u16)version; +#endif + buffer_position += 2; + + auto length = AK::convert_between_host_and_network_endian(*(const u16*)buffer.offset_pointer(buffer_position)); +#ifdef TLS_DEBUG + dbg() << "record length: " << length << " at offset: " << buffer_position; +#endif + buffer_position += 2; + + if (buffer_position + length > buffer.size()) { +#ifdef TLS_DEBUG + dbg() << "record length more than what we have: " << buffer.size(); +#endif + return (i8)Error::NeedMoreData; + } + +#ifdef TLS_DEBUG + dbg() << "message type: " << (u8)type << ", length: " << length; +#endif + auto plain = buffer.slice(buffer_position, buffer.size() - buffer_position); + + ByteBuffer decrypted; + + if (m_context.cipher_spec_set && type != MessageType::ChangeCipher) { +#ifdef TLS_DEBUG + dbgln("Encrypted: "); + print_buffer(buffer.slice(header_size, length)); +#endif + + if (is_aead()) { + ASSERT(m_aes_remote.gcm); + + if (length < 24) { + dbgln("Invalid packet length"); + auto packet = build_alert(true, (u8)AlertDescription::DecryptError); + write_packet(packet); + return (i8)Error::BrokenPacket; + } + + auto packet_length = length - iv_length() - 16; + auto payload = plain; + decrypted = ByteBuffer::create_uninitialized(packet_length); + + // AEAD AAD (13) + // Seq. no (8) + // content type (1) + // version (2) + // length (2) + u8 aad[13]; + Bytes aad_bytes { aad, 13 }; + OutputMemoryStream aad_stream { aad_bytes }; + + u64 seq_no = AK::convert_between_host_and_network_endian(m_context.remote_sequence_number); + u16 len = AK::convert_between_host_and_network_endian((u16)packet_length); + + aad_stream.write({ &seq_no, sizeof(seq_no) }); // Sequence number + aad_stream.write(buffer.slice(0, header_size - 2)); // content-type + version + aad_stream.write({ &len, sizeof(u16) }); + ASSERT(aad_stream.is_end()); + + auto nonce = payload.slice(0, iv_length()); + payload = payload.slice(iv_length()); + + // AEAD IV (12) + // IV (4) + // (Nonce) (8) + // -- Our GCM impl takes 16 bytes + // zero (4) + u8 iv[16]; + Bytes iv_bytes { iv, 16 }; + Bytes { m_context.crypto.remote_aead_iv, 4 }.copy_to(iv_bytes); + nonce.copy_to(iv_bytes.slice(4)); + memset(iv_bytes.offset(12), 0, 4); + + auto ciphertext = payload.slice(0, payload.size() - 16); + auto tag = payload.slice(ciphertext.size()); + + auto consistency = m_aes_remote.gcm->decrypt( + ciphertext, + decrypted, + iv_bytes, + aad_bytes, + tag); + + if (consistency != Crypto::VerificationConsistency::Consistent) { + dbg() << "integrity check failed (tag length " << tag.size() << ")"; + auto packet = build_alert(true, (u8)AlertDescription::BadRecordMAC); + write_packet(packet); + + return (i8)Error::IntegrityCheckFailed; + } + + plain = decrypted; + } else { + ASSERT(m_aes_remote.cbc); + auto iv_size = iv_length(); + + decrypted = m_aes_remote.cbc->create_aligned_buffer(length - iv_size); + auto iv = buffer.slice(header_size, iv_size); + + Bytes decrypted_span = decrypted; + m_aes_remote.cbc->decrypt(buffer.slice(header_size + iv_size, length - iv_size), decrypted_span, iv); + + length = decrypted_span.size(); + +#ifdef TLS_DEBUG + dbgln("Decrypted: "); + print_buffer(decrypted); +#endif + + auto mac_size = mac_length(); + if (length < mac_size) { + dbgln("broken packet"); + auto packet = build_alert(true, (u8)AlertDescription::DecryptError); + write_packet(packet); + return (i8)Error::BrokenPacket; + } + + length -= mac_size; + + const u8* message_hmac = decrypted_span.offset(length); + u8 temp_buf[5]; + memcpy(temp_buf, buffer.offset_pointer(0), 3); + *(u16*)(temp_buf + 3) = AK::convert_between_host_and_network_endian(length); + auto hmac = hmac_message({ temp_buf, 5 }, decrypted_span.slice(0, length), mac_size); + auto message_mac = ReadonlyBytes { message_hmac, mac_size }; + if (hmac != message_mac) { + dbg() << "integrity check failed (mac length " << mac_size << ")"; + dbgln("mac received:"); + print_buffer(message_mac); + dbgln("mac computed:"); + print_buffer(hmac); + auto packet = build_alert(true, (u8)AlertDescription::BadRecordMAC); + write_packet(packet); + + return (i8)Error::IntegrityCheckFailed; + } + plain = decrypted.bytes().slice(0, length); + } + } + m_context.remote_sequence_number++; + + switch (type) { + case MessageType::ApplicationData: + if (m_context.connection_status != ConnectionStatus::Established) { + dbgln("unexpected application data"); + payload_res = (i8)Error::UnexpectedMessage; + auto packet = build_alert(true, (u8)AlertDescription::UnexpectedMessage); + write_packet(packet); + } else { +#ifdef TLS_DEBUG + dbg() << "application data message of size " << plain.size(); +#endif + + m_context.application_buffer.append(plain.data(), plain.size()); + } + break; + case MessageType::Handshake: +#ifdef TLS_DEBUG + dbgln("tls handshake message"); +#endif + payload_res = handle_payload(plain); + break; + case MessageType::ChangeCipher: + if (m_context.connection_status != ConnectionStatus::KeyExchange) { + dbgln("unexpected change cipher message"); + auto packet = build_alert(true, (u8)AlertDescription::UnexpectedMessage); + payload_res = (i8)Error::UnexpectedMessage; + } else { +#ifdef TLS_DEBUG + dbgln("change cipher spec message"); +#endif + m_context.cipher_spec_set = true; + m_context.remote_sequence_number = 0; + } + break; + case MessageType::Alert: +#ifdef TLS_DEBUG + dbg() << "alert message of length " << length; +#endif + if (length >= 2) { +#ifdef TLS_DEBUG + print_buffer(plain); +#endif + auto level = plain[0]; + auto code = plain[1]; + if (level == (u8)AlertLevel::Critical) { + dbg() << "We were alerted of a critical error: " << code << " (" << alert_name((AlertDescription)code) << ")"; + m_context.critical_error = code; + try_disambiguate_error(); + res = (i8)Error::UnknownError; + } else { + dbg() << "Alert: " << code; + } + if (code == 0) { + // close notify + res += 2; + alert(AlertLevel::Critical, AlertDescription::CloseNotify); + m_context.connection_finished = true; + if (!m_context.cipher_spec_set) { + // AWS CloudFront hits this. + dbgln("Server sent a close notify and we haven't agreed on a cipher suite. Treating it as a handshake failure."); + m_context.critical_error = (u8)AlertDescription::HandshakeFailure; + try_disambiguate_error(); + } + } + m_context.error_code = (Error)code; + } + break; + default: + dbgln("message not understood"); + return (i8)Error::NotUnderstood; + } + + if (payload_res < 0) + return payload_res; + + if (res > 0) + return header_size + length; + + return res; +} + +} diff --git a/Userland/Libraries/LibTLS/Socket.cpp b/Userland/Libraries/LibTLS/Socket.cpp new file mode 100644 index 0000000000..fd7c97761f --- /dev/null +++ b/Userland/Libraries/LibTLS/Socket.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibCore/DateTime.h> +#include <LibCore/Timer.h> +#include <LibCrypto/ASN1/DER.h> +#include <LibCrypto/PK/Code/EMSA_PSS.h> +#include <LibTLS/TLSv12.h> + +namespace TLS { + +Optional<ByteBuffer> TLSv12::read() +{ + if (m_context.application_buffer.size()) { + auto buf = m_context.application_buffer.slice(0, m_context.application_buffer.size()); + m_context.application_buffer.clear(); + return buf; + } + return {}; +} + +ByteBuffer TLSv12::read(size_t max_size) +{ + if (m_context.application_buffer.size()) { + auto length = min(m_context.application_buffer.size(), max_size); + auto buf = m_context.application_buffer.slice(0, length); + m_context.application_buffer = m_context.application_buffer.slice(length, m_context.application_buffer.size() - length); + return buf; + } + return {}; +} + +String TLSv12::read_line(size_t max_size) +{ + if (!can_read_line()) + return {}; + + auto* start = m_context.application_buffer.data(); + auto* newline = (u8*)memchr(m_context.application_buffer.data(), '\n', m_context.application_buffer.size()); + ASSERT(newline); + + size_t offset = newline - start; + + if (offset > max_size) + return {}; + + auto buffer = ByteBuffer::copy(start, offset); + m_context.application_buffer = m_context.application_buffer.slice(offset + 1, m_context.application_buffer.size() - offset - 1); + + return String::copy(buffer, Chomp); +} + +bool TLSv12::write(ReadonlyBytes buffer) +{ + if (m_context.connection_status != ConnectionStatus::Established) { +#ifdef TLS_DEBUG + dbgln("write request while not connected"); +#endif + return false; + } + + PacketBuilder builder { MessageType::ApplicationData, m_context.version, buffer.size() }; + builder.append(buffer); + auto packet = builder.build(); + + update_packet(packet); + write_packet(packet); + + return true; +} + +bool TLSv12::connect(const String& hostname, int port) +{ + set_sni(hostname); + return Core::Socket::connect(hostname, port); +} + +bool TLSv12::common_connect(const struct sockaddr* saddr, socklen_t length) +{ + if (m_context.critical_error) + return false; + + if (Core::Socket::is_connected()) { + if (is_established()) { + ASSERT_NOT_REACHED(); + } else { + Core::Socket::close(); // reuse? + } + } + + Core::Socket::on_connected = [this] { + Core::Socket::on_ready_to_read = [this] { + read_from_socket(); + }; + + auto packet = build_hello(); + write_packet(packet); + + deferred_invoke([&](auto&) { + m_handshake_timeout_timer = Core::Timer::create_single_shot( + m_max_wait_time_for_handshake_in_seconds * 1000, [&] { + auto timeout_diff = Core::DateTime::now().timestamp() - m_context.handshake_initiation_timestamp; + // If the timeout duration was actually within the max wait time (with a margin of error), + // we're not operating slow, so the server timed out. + // otherwise, it's our fault that the negotiation is taking too long, so extend the timer :P + if (timeout_diff < m_max_wait_time_for_handshake_in_seconds + 1) { + // The server did not respond fast enough, + // time the connection out. + alert(AlertLevel::Critical, AlertDescription::UserCanceled); + m_context.connection_finished = true; + m_context.tls_buffer.clear(); + m_context.error_code = Error::TimedOut; + m_context.critical_error = (u8)Error::TimedOut; + check_connection_state(false); // Notify the client. + } else { + // Extend the timer, we are too slow. + m_handshake_timeout_timer->restart(m_max_wait_time_for_handshake_in_seconds * 1000); + } + }, + this); + write_into_socket(); + m_handshake_timeout_timer->start(); + m_context.handshake_initiation_timestamp = Core::DateTime::now().timestamp(); + }); + m_has_scheduled_write_flush = true; + + if (on_tls_connected) + on_tls_connected(); + }; + bool success = Core::Socket::common_connect(saddr, length); + if (!success) + return false; + + return true; +} + +void TLSv12::read_from_socket() +{ + if (m_context.application_buffer.size() > 0) { + deferred_invoke([&](auto&) { read_from_socket(); }); + if (on_tls_ready_to_read) + on_tls_ready_to_read(*this); + } + + if (!check_connection_state(true)) + return; + + consume(Core::Socket::read(4096)); +} + +void TLSv12::write_into_socket() +{ +#ifdef TLS_DEBUG + dbg() << "Flushing cached records: " << m_context.tls_buffer.size() << " established? " << is_established(); +#endif + m_has_scheduled_write_flush = false; + if (!check_connection_state(false)) + return; + flush(); + + if (!is_established()) + return; + + if (!m_context.application_buffer.size()) // hey client, you still have stuff to read... + if (on_tls_ready_to_write) + on_tls_ready_to_write(*this); +} + +bool TLSv12::check_connection_state(bool read) +{ + if (!Core::Socket::is_open() || !Core::Socket::is_connected() || Core::Socket::eof()) { + // an abrupt closure (the server is a jerk) +#ifdef TLS_DEBUG + dbgln("Socket not open, assuming abrupt closure"); +#endif + m_context.connection_finished = true; + } + if (m_context.critical_error) { +#ifdef TLS_DEBUG + dbg() << "CRITICAL ERROR " << m_context.critical_error << " :("; +#endif + if (on_tls_error) + on_tls_error((AlertDescription)m_context.critical_error); + return false; + } + if (((read && m_context.application_buffer.size() == 0) || !read) && m_context.connection_finished) { + if (m_context.application_buffer.size() == 0) { + if (on_tls_finished) + on_tls_finished(); + } + if (m_context.tls_buffer.size()) { +#ifdef TLS_DEBUG + dbg() << "connection closed without finishing data transfer, " << m_context.tls_buffer.size() << " bytes still in buffer & " << m_context.application_buffer.size() << " bytes in application buffer"; +#endif + } else { + m_context.connection_finished = false; +#ifdef TLS_DEBUG + dbgln("FINISHED"); +#endif + } + if (!m_context.application_buffer.size()) { + m_context.connection_status = ConnectionStatus::Disconnected; + return false; + } + } + return true; +} + +bool TLSv12::flush() +{ + auto out_buffer = write_buffer().data(); + size_t out_buffer_index { 0 }; + size_t out_buffer_length = write_buffer().size(); + + if (out_buffer_length == 0) + return true; + +#ifdef TLS_DEBUG + dbgln("SENDING..."); + print_buffer(out_buffer, out_buffer_length); +#endif + if (Core::Socket::write(&out_buffer[out_buffer_index], out_buffer_length)) { + write_buffer().clear(); + return true; + } + if (m_context.send_retries++ == 10) { + // drop the records, we can't send +#ifdef TLS_DEBUG + dbg() << "Dropping " << write_buffer().size() << " bytes worth of TLS records as max retries has been reached"; +#endif + write_buffer().clear(); + m_context.send_retries = 0; + } + return false; +} + +} diff --git a/Userland/Libraries/LibTLS/TLSPacketBuilder.h b/Userland/Libraries/LibTLS/TLSPacketBuilder.h new file mode 100644 index 0000000000..2994ed27ab --- /dev/null +++ b/Userland/Libraries/LibTLS/TLSPacketBuilder.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/ByteBuffer.h> +#include <AK/Endian.h> +#include <AK/Types.h> + +namespace TLS { + +enum class MessageType : u8 { + ChangeCipher = 0x14, + Alert = 0x15, + Handshake = 0x16, + ApplicationData = 0x17, +}; + +enum class Version : u16 { + V10 = 0x0301, + V11 = 0x0302, + V12 = 0x0303, + V13 = 0x0304 +}; + +class PacketBuilder { +public: + PacketBuilder(MessageType type, u16 version, size_t size_hint = 0xfdf) + : PacketBuilder(type, (Version)version, size_hint) + { + } + + PacketBuilder(MessageType type, Version version, size_t size_hint = 0xfdf) + { + m_packet_data = ByteBuffer::create_uninitialized(size_hint + 16); + m_current_length = 5; + m_packet_data[0] = (u8)type; + *(u16*)m_packet_data.offset_pointer(1) = AK::convert_between_host_and_network_endian((u16)version); + } + + inline void append(u16 value) + { + value = AK::convert_between_host_and_network_endian(value); + append((const u8*)&value, sizeof(value)); + } + inline void append(u8 value) + { + append((const u8*)&value, sizeof(value)); + } + inline void append(ReadonlyBytes data) + { + append(data.data(), data.size()); + } + inline void append_u24(u32 value) + { + u8 buf[3]; + buf[0] = value / 0x10000; + value %= 0x10000; + buf[1] = value / 0x100; + value %= 0x100; + buf[2] = value; + + append(buf, 3); + } + inline void append(const u8* data, size_t bytes) + { + if (bytes == 0) + return; + + auto old_length = m_current_length; + m_current_length += bytes; + + if (m_packet_data.size() < m_current_length) { + m_packet_data.grow(m_current_length); + } + + m_packet_data.overwrite(old_length, data, bytes); + } + inline ByteBuffer build() + { + auto length = m_current_length; + m_current_length = 0; + return m_packet_data.slice(0, length); + } + inline void set(size_t offset, u8 value) + { + ASSERT(offset < m_current_length); + m_packet_data[offset] = value; + } + size_t length() const { return m_current_length; } + +private: + ByteBuffer m_packet_data; + size_t m_current_length; +}; + +} diff --git a/Userland/Libraries/LibTLS/TLSv12.cpp b/Userland/Libraries/LibTLS/TLSv12.cpp new file mode 100644 index 0000000000..272f12b961 --- /dev/null +++ b/Userland/Libraries/LibTLS/TLSv12.cpp @@ -0,0 +1,887 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/Endian.h> +#include <LibCore/ConfigFile.h> +#include <LibCore/DateTime.h> +#include <LibCore/Timer.h> +#include <LibCrypto/ASN1/DER.h> +#include <LibCrypto/ASN1/PEM.h> +#include <LibCrypto/PK/Code/EMSA_PSS.h> +#include <LibTLS/TLSv12.h> + +#ifndef SOCK_NONBLOCK +# include <sys/ioctl.h> +#endif + +//#define TLS_DEBUG + +namespace { +struct OIDChain { + OIDChain* root { nullptr }; + u8* oid { nullptr }; +}; +} + +namespace TLS { + +// "for now" q&d implementation of ASN1 +namespace { + +static bool _asn1_is_field_present(const u32* fields, const u32* prefix) +{ + size_t i = 0; + while (prefix[i]) { + if (fields[i] != prefix[i]) + return false; + ++i; + } + return true; +} + +static bool _asn1_is_oid(const u8* oid, const u8* compare, size_t length = 3) +{ + size_t i = 0; + while (oid[i] && i < length) { + if (oid[i] != compare[i]) + return false; + ++i; + } + return true; +} + +static bool _asn1_is_oid_in_chain(OIDChain* reference_chain, const u8* lookup, size_t lookup_length = 3) +{ + auto is_oid = [](const u8* oid, size_t oid_length, const u8* compare, size_t compare_length) { + if (oid_length < compare_length) + compare_length = oid_length; + for (size_t i = 0; i < compare_length; i++) { + if (oid[i] != compare[i]) + return false; + } + return true; + }; + for (; reference_chain; reference_chain = reference_chain->root) { + if (reference_chain->oid) + if (is_oid(reference_chain->oid, 16, lookup, lookup_length)) + return true; + } + return false; +} + +static bool _set_algorithm(CertificateKeyAlgorithm& algorithm, const u8* value, size_t length) +{ + if (length == 7) { + // Elliptic Curve pubkey + dbgln("Cert.algorithm: EC, unsupported"); + return false; + } + + if (length == 8) { + // named EC key + dbg() << "Cert.algorithm: Named EC (" << *value << "), unsupported"; + return false; + } + + if (length == 5) { + // named EC SECP key + dbg() << "Cert.algorithm: Named EC secp (" << *value << "), unsupported"; + return false; + } + + if (length != 9) { + dbgln("Invalid certificate algorithm"); + return false; + } + + if (_asn1_is_oid(value, Constants::RSA_SIGN_RSA_OID, 9)) { + algorithm = CertificateKeyAlgorithm::RSA_RSA; + return true; + } + + if (_asn1_is_oid(value, Constants::RSA_SIGN_SHA256_OID, 9)) { + algorithm = CertificateKeyAlgorithm::RSA_SHA256; + return true; + } + + if (_asn1_is_oid(value, Constants::RSA_SIGN_SHA512_OID, 9)) { + algorithm = CertificateKeyAlgorithm::RSA_SHA512; + return true; + } + + if (_asn1_is_oid(value, Constants::RSA_SIGN_SHA1_OID, 9)) { + algorithm = CertificateKeyAlgorithm::RSA_SHA1; + return true; + } + + if (_asn1_is_oid(value, Constants::RSA_SIGN_MD5_OID, 9)) { + algorithm = CertificateKeyAlgorithm::RSA_MD5; + return true; + } + + dbg() << "Unsupported RSA Signature mode " << value[8]; + return false; +} + +static size_t _get_asn1_length(const u8* buffer, size_t length, size_t& octets) +{ + octets = 0; + if (length < 1) + return 0; + + u8 size = buffer[0]; + if (size & 0x80) { + octets = size & 0x7f; + if (octets > length - 1) { + return 0; + } + auto reference_octets = octets; + if (octets > 4) + reference_octets = 4; + size_t long_size = 0, coeff = 1; + for (auto i = reference_octets; i > 0; --i) { + long_size += buffer[i] * coeff; + coeff *= 0x100; + } + ++octets; + return long_size; + } + ++octets; + return size; +} + +static ssize_t _parse_asn1(const Context& context, Certificate& cert, const u8* buffer, size_t size, int level, u32* fields, u8* has_key, int client_cert, u8* root_oid, OIDChain* chain) +{ + OIDChain local_chain; + local_chain.root = chain; + size_t position = 0; + + // parse DER...again + size_t index = 0; + u8 oid[16] { 0 }; + + local_chain.oid = oid; + if (has_key) + *has_key = 0; + + u8 local_has_key = 0; + const u8* cert_data = nullptr; + size_t cert_length = 0; + while (position < size) { + size_t start_position = position; + if (size - position < 2) { + dbgln("not enough data for certificate size"); + return (i8)Error::NeedMoreData; + } + u8 first = buffer[position++]; + u8 type = first & 0x1f; + u8 constructed = first & 0x20; + size_t octets = 0; + u32 temp; + index++; + + if (level <= 0xff) + fields[level - 1] = index; + + size_t length = _get_asn1_length((const u8*)&buffer[position], size - position, octets); + + if (octets > 4 || octets > size - position) { +#ifdef TLS_DEBUG + dbgln("could not read the certificate"); +#endif + return position; + } + + position += octets; + if (size - position < length) { +#ifdef TLS_DEBUG + dbgln("not enough data for sequence"); +#endif + return (i8)Error::NeedMoreData; + } + + if (length && constructed) { + switch (type) { + case 0x03: + break; + case 0x10: + if (level == 2 && index == 1) { + cert_length = length + position - start_position; + cert_data = buffer + start_position; + } + // public key data + if (!cert.version && _asn1_is_field_present(fields, Constants::priv_der_id)) { + temp = length + position - start_position; + if (cert.der.size() < temp) { + cert.der.grow(temp); + } else { + cert.der.trim(temp); + } + cert.der.overwrite(0, buffer + start_position, temp); + } + break; + + default: + break; + } + local_has_key = false; + _parse_asn1(context, cert, buffer + position, length, level + 1, fields, &local_has_key, client_cert, root_oid, &local_chain); + if ((local_has_key && (!context.is_server || client_cert)) || (client_cert || _asn1_is_field_present(fields, Constants::pk_id))) { + temp = length + position - start_position; + if (cert.der.size() < temp) { + cert.der.grow(temp); + } else { + cert.der.trim(temp); + } + cert.der.overwrite(0, buffer + start_position, temp); + } + } else { + switch (type) { + case 0x00: + return position; + break; + case 0x01: + temp = buffer[position]; + break; + case 0x02: + if (_asn1_is_field_present(fields, Constants::pk_id)) { + if (has_key) + *has_key = true; + + if (index == 1) + cert.public_key.set( + Crypto::UnsignedBigInteger::import_data(buffer + position, length), + cert.public_key.public_exponent()); + else if (index == 2) + cert.public_key.set( + cert.public_key.modulus(), + Crypto::UnsignedBigInteger::import_data(buffer + position, length)); + } else if (_asn1_is_field_present(fields, Constants::serial_id)) { + cert.serial_number = Crypto::UnsignedBigInteger::import_data(buffer + position, length); + } + if (_asn1_is_field_present(fields, Constants::version_id)) { + if (length == 1) + cert.version = buffer[position]; + } + if (chain && length > 2) { + if (_asn1_is_oid_in_chain(chain, Constants::san_oid)) { + StringView alt_name { &buffer[position], length }; + cert.SAN.append(alt_name); + } + } + // print_buffer(ReadonlyBytes { buffer + position, length }); + break; + case 0x03: + if (_asn1_is_field_present(fields, Constants::pk_id)) { + if (has_key) + *has_key = true; + } + if (_asn1_is_field_present(fields, Constants::sign_id)) { + auto* value = buffer + position; + auto len = length; + if (!value[0] && len % 2) { + ++value; + --len; + } + cert.sign_key = ByteBuffer::copy(value, len); + } else { + if (buffer[position] == 0 && length > 256) { + _parse_asn1(context, cert, buffer + position + 1, length - 1, level + 1, fields, &local_has_key, client_cert, root_oid, &local_chain); + } else { + _parse_asn1(context, cert, buffer + position, length, level + 1, fields, &local_has_key, client_cert, root_oid, &local_chain); + } + } + break; + case 0x04: + _parse_asn1(context, cert, buffer + position, length, level + 1, fields, &local_has_key, client_cert, root_oid, &local_chain); + break; + case 0x05: + break; + case 0x06: + if (_asn1_is_field_present(fields, Constants::pk_id)) { + _set_algorithm(cert.key_algorithm, buffer + position, length); + } + if (_asn1_is_field_present(fields, Constants::algorithm_id)) { + _set_algorithm(cert.algorithm, buffer + position, length); + } + + if (length < 16) + memcpy(oid, buffer + position, length); + else + memcpy(oid, buffer + position, 16); + if (root_oid) + memcpy(root_oid, oid, 16); + break; + case 0x09: + break; + case 0x17: + case 0x018: + // time + // ignore + break; + case 0x013: + case 0x0c: + case 0x14: + case 0x15: + case 0x16: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: + // printable string and such + if (_asn1_is_field_present(fields, Constants::issurer_id)) { + if (_asn1_is_oid(oid, Constants::country_oid)) { + cert.issuer_country = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::state_oid)) { + cert.issuer_state = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::location_oid)) { + cert.issuer_location = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::entity_oid)) { + cert.issuer_entity = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::subject_oid)) { + cert.issuer_subject = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::unit_oid)) { + cert.issuer_unit = String { (const char*)buffer + position, length }; + } + } else if (_asn1_is_field_present(fields, Constants::owner_id)) { + if (_asn1_is_oid(oid, Constants::country_oid)) { + cert.country = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::state_oid)) { + cert.state = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::location_oid)) { + cert.location = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::entity_oid)) { + cert.entity = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::subject_oid)) { + cert.subject = String { (const char*)buffer + position, length }; + } else if (_asn1_is_oid(oid, Constants::unit_oid)) { + cert.unit = String { (const char*)buffer + position, length }; + } + } + break; + default: + // dbg() << "unused field " << type; + break; + } + } + position += length; + } + if (level == 2 && cert.sign_key.size() && cert_length && cert_data) { + cert.fingerprint.clear(); + Crypto::Hash::Manager hash; + switch (cert.key_algorithm) { + case CertificateKeyAlgorithm::RSA_MD5: + hash.initialize(Crypto::Hash::HashKind::MD5); + break; + case CertificateKeyAlgorithm::RSA_SHA1: + hash.initialize(Crypto::Hash::HashKind::SHA1); + break; + case CertificateKeyAlgorithm::RSA_SHA256: + hash.initialize(Crypto::Hash::HashKind::SHA256); + break; + case CertificateKeyAlgorithm::RSA_SHA512: + hash.initialize(Crypto::Hash::HashKind::SHA512); + break; + default: +#ifdef TLS_DEBUG + dbg() << "Unsupported hash mode " << (u32)cert.key_algorithm; +#endif + // fallback to md5, it will fail later + hash.initialize(Crypto::Hash::HashKind::MD5); + break; + } + hash.update(cert_data, cert_length); + auto fingerprint = hash.digest(); + cert.fingerprint.grow(fingerprint.data_length()); + cert.fingerprint.overwrite(0, fingerprint.immutable_data(), fingerprint.data_length()); +#ifdef TLS_DEBUG + dbgln("Certificate fingerprint:"); + print_buffer(cert.fingerprint); +#endif + } + return position; +} +} + +Optional<Certificate> TLSv12::parse_asn1(ReadonlyBytes buffer, bool) const +{ + // FIXME: Our ASN.1 parser is not quite up to the task of + // parsing this X.509 certificate, so for the + // time being, we will "parse" the certificate + // manually right here. + + Certificate cert; + u32 fields[0xff]; + + _parse_asn1(m_context, cert, buffer.data(), buffer.size(), 1, fields, nullptr, 0, nullptr, nullptr); + +#ifdef TLS_DEBUG + dbg() << "Certificate issued for " << cert.subject << " by " << cert.issuer_subject; +#endif + + return cert; +} + +ssize_t TLSv12::handle_certificate(ReadonlyBytes buffer) +{ + ssize_t res = 0; + + if (buffer.size() < 3) { +#ifdef TLS_DEBUG + dbgln("not enough certificate header data"); +#endif + return (i8)Error::NeedMoreData; + } + + u32 certificate_total_length = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2]; + +#ifdef TLS_DEBUG + dbg() << "total length: " << certificate_total_length; +#endif + + if (certificate_total_length <= 4) + return 3 * certificate_total_length; + + res += 3; + + if (certificate_total_length > buffer.size() - res) { +#ifdef TLS_DEBUG + dbgln("not enough data for claimed total cert length"); +#endif + return (i8)Error::NeedMoreData; + } + size_t size = certificate_total_length; + + size_t index = 0; + bool valid_certificate = false; + + while (size > 0) { + ++index; + if (buffer.size() - res < 3) { +#ifdef TLS_DEBUG + dbgln("not enough data for certificate length"); +#endif + return (i8)Error::NeedMoreData; + } + size_t certificate_size = buffer[res] * 0x10000 + buffer[res + 1] * 0x100 + buffer[res + 2]; + res += 3; + + if (buffer.size() - res < certificate_size) { +#ifdef TLS_DEBUG + dbgln("not enough data for certificate body"); +#endif + return (i8)Error::NeedMoreData; + } + + auto res_cert = res; + auto remaining = certificate_size; + size_t certificates_in_chain = 0; + + do { + if (remaining <= 3) { + dbgln("Ran out of data"); + break; + } + ++certificates_in_chain; + if (buffer.size() < (size_t)res_cert + 3) { + dbg() << "not enough data to read cert size (" << buffer.size() << " < " << res_cert + 3 << ")"; + break; + } + size_t certificate_size_specific = buffer[res_cert] * 0x10000 + buffer[res_cert + 1] * 0x100 + buffer[res_cert + 2]; + res_cert += 3; + remaining -= 3; + + if (certificate_size_specific > remaining) { + dbg() << "invalid certificate size (expected " << remaining << " but got " << certificate_size_specific << ")"; + break; + } + remaining -= certificate_size_specific; + + auto certificate = parse_asn1(buffer.slice(res_cert, certificate_size_specific), false); + if (certificate.has_value()) { + if (certificate.value().is_valid()) { + m_context.certificates.append(certificate.value()); + valid_certificate = true; + } + } + res_cert += certificate_size_specific; + } while (remaining > 0); + if (remaining) { + dbg() << "extraneous " << remaining << " bytes left over after parsing certificates"; + } + size -= certificate_size + 3; + res += certificate_size; + } + if (!valid_certificate) + return (i8)Error::UnsupportedCertificate; + + if ((size_t)res != buffer.size()) + dbg() << "some data left unread: " << (size_t)res << " bytes out of " << buffer.size(); + + return res; +} + +void TLSv12::consume(ReadonlyBytes record) +{ + if (m_context.critical_error) { + dbg() << "There has been a critical error (" << (i8)m_context.critical_error << "), refusing to continue"; + return; + } + + if (record.size() == 0) { + return; + } + +#ifdef TLS_DEBUG + dbg() << "Consuming " << record.size() << " bytes"; +#endif + + m_context.message_buffer.append(record.data(), record.size()); + + size_t index { 0 }; + size_t buffer_length = m_context.message_buffer.size(); + + size_t size_offset { 3 }; // read the common record header + size_t header_size { 5 }; +#ifdef TLS_DEBUG + dbg() << "message buffer length " << buffer_length; +#endif + while (buffer_length >= 5) { + auto length = AK::convert_between_host_and_network_endian(*(u16*)m_context.message_buffer.offset_pointer(index + size_offset)) + header_size; + if (length > buffer_length) { +#ifdef TLS_DEBUG + dbg() << "Need more data: " << length << " | " << buffer_length; +#endif + break; + } + auto consumed = handle_message(m_context.message_buffer.bytes().slice(index, length)); + +#ifdef TLS_DEBUG + if (consumed > 0) + dbg() << "consumed " << (size_t)consumed << " bytes"; + else + dbg() << "error: " << (int)consumed; +#endif + + if (consumed != (i8)Error::NeedMoreData) { + if (consumed < 0) { + dbg() << "Consumed an error: " << (int)consumed; + if (!m_context.critical_error) + m_context.critical_error = (i8)consumed; + m_context.error_code = (Error)consumed; + break; + } + } else { + continue; + } + + index += length; + buffer_length -= length; + if (m_context.critical_error) { + dbgln("Broken connection"); + m_context.error_code = Error::BrokenConnection; + break; + } + } + if (m_context.error_code != Error::NoError && m_context.error_code != Error::NeedMoreData) { + dbg() << "consume error: " << (i8)m_context.error_code; + m_context.message_buffer.clear(); + return; + } + + if (index) { + m_context.message_buffer = m_context.message_buffer.slice(index, m_context.message_buffer.size() - index); + } +} + +void TLSv12::ensure_hmac(size_t digest_size, bool local) +{ + if (local && m_hmac_local) + return; + + if (!local && m_hmac_remote) + return; + + auto hash_kind = Crypto::Hash::HashKind::None; + + switch (digest_size) { + case Crypto::Hash::SHA1::DigestSize: + hash_kind = Crypto::Hash::HashKind::SHA1; + break; + case Crypto::Hash::SHA256::DigestSize: + hash_kind = Crypto::Hash::HashKind::SHA256; + break; + case Crypto::Hash::SHA512::DigestSize: + hash_kind = Crypto::Hash::HashKind::SHA512; + break; + default: + dbg() << "Failed to find a suitable hash for size " << digest_size; + break; + } + + auto hmac = make<Crypto::Authentication::HMAC<Crypto::Hash::Manager>>(ReadonlyBytes { local ? m_context.crypto.local_mac : m_context.crypto.remote_mac, digest_size }, hash_kind); + if (local) + m_hmac_local = move(hmac); + else + m_hmac_remote = move(hmac); +} + +bool Certificate::is_valid() const +{ + auto now = Core::DateTime::now(); + + if (!not_before.is_empty()) { + if (now.is_before(not_before)) { + dbg() << "certificate expired (not yet valid, signed for " << not_before << ")"; + return false; + } + } + + if (!not_after.is_empty()) { + if (!now.is_before(not_after)) { + dbg() << "certificate expired (expiry date " << not_after << ")"; + return false; + } + } + + return true; +} + +void TLSv12::try_disambiguate_error() const +{ + dbgln("Possible failure cause(s): "); + switch ((AlertDescription)m_context.critical_error) { + case AlertDescription::HandshakeFailure: + if (!m_context.cipher_spec_set) { + dbg() << "- No cipher suite in common with " << m_context.SNI; + } else { + dbgln("- Unknown internal issue"); + } + break; + case AlertDescription::InsufficientSecurity: + dbg() << "- No cipher suite in common with " << m_context.SNI << " (the server is oh so secure)"; + break; + case AlertDescription::ProtocolVersion: + dbgln("- The server refused to negotiate with TLS 1.2 :("); + break; + case AlertDescription::UnexpectedMessage: + dbgln("- We sent an invalid message for the state we're in."); + break; + case AlertDescription::BadRecordMAC: + dbgln("- Bad MAC record from our side."); + dbgln("- Ciphertext wasn't an even multiple of the block length."); + dbgln("- Bad block cipher padding."); + dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network."); + break; + case AlertDescription::RecordOverflow: + dbgln("- Sent a ciphertext record which has a length bigger than 18432 bytes."); + dbgln("- Sent record decrypted to a compressed record that has a length bigger than 18432 bytes."); + dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network."); + break; + case AlertDescription::DecompressionFailure: + dbgln("- We sent invalid input for decompression (e.g. data that would expand to excessive length)"); + break; + case AlertDescription::IllegalParameter: + dbgln("- We sent a parameter in the handshake that is out of range or inconsistent with the other parameters."); + break; + case AlertDescription::DecodeError: + dbgln("- The message we sent cannot be decoded because a field was out of range or the length was incorrect."); + dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network."); + break; + case AlertDescription::DecryptError: + dbgln("- A handshake crypto operation failed. This includes signature verification and validating Finished."); + break; + case AlertDescription::AccessDenied: + dbgln("- The certificate is valid, but once access control was applied, the sender decided to stop negotiation."); + break; + case AlertDescription::InternalError: + dbgln("- No one knows, but it isn't a protocol failure."); + break; + case AlertDescription::DecryptionFailed: + case AlertDescription::NoCertificate: + case AlertDescription::ExportRestriction: + dbgln("- No one knows, the server sent a non-compliant alert."); + break; + default: + dbgln("- No one knows."); + break; + } +} + +void TLSv12::set_root_certificates(Vector<Certificate> certificates) +{ + if (!m_context.root_ceritificates.is_empty()) + dbgln("TLS warn: resetting root certificates!"); + + for (auto& cert : certificates) { + if (!cert.is_valid()) + dbg() << "Certificate for " << cert.subject << " by " << cert.issuer_subject << " is invalid, things may or may not work!"; + // FIXME: Figure out what we should do when our root certs are invalid. + } + m_context.root_ceritificates = move(certificates); +} + +bool Context::verify_chain() const +{ + const Vector<Certificate>* local_chain = nullptr; + if (is_server) { + dbgln("Unsupported: Server mode"); + TODO(); + } else { + local_chain = &certificates; + } + + // FIXME: Actually verify the signature, instead of just checking the name. + HashMap<String, String> chain; + HashTable<String> roots; + // First, walk the root certs. + for (auto& cert : root_ceritificates) { + roots.set(cert.subject); + chain.set(cert.subject, cert.issuer_subject); + } + + // Then, walk the local certs. + for (auto& cert : *local_chain) { + auto& issuer_unique_name = cert.issuer_unit.is_empty() ? cert.issuer_subject : cert.issuer_unit; + chain.set(cert.subject, issuer_unique_name); + } + + // Then verify the chain. + for (auto& it : chain) { + if (it.key == it.value) { // Allow self-signed certificates. + if (!roots.contains(it.key)) + dbg() << "Self-signed warning: Certificate for " << it.key << " is self-signed"; + continue; + } + + auto ref = chain.get(it.value); + if (!ref.has_value()) { + dbg() << "Certificate for " << it.key << " is not signed by anyone we trust (" << it.value << ")"; + return false; + } + + if (ref.value() == it.key) // Allow (but warn about) mutually recursively signed cert A <-> B. + dbg() << "Co-dependency warning: Certificate for " << ref.value() << " is issued by " << it.key << ", which itself is issued by " << ref.value(); + } + + return true; +} + +static bool wildcard_matches(const StringView& host, const StringView& subject) +{ + if (host.matches(subject)) + return true; + + if (subject.starts_with("*.")) + return wildcard_matches(host, subject.substring_view(2)); + + return false; +} + +Optional<size_t> TLSv12::verify_chain_and_get_matching_certificate(const StringView& host) const +{ + if (m_context.certificates.is_empty() || !m_context.verify_chain()) + return {}; + + if (host.is_empty()) + return 0; + + for (size_t i = 0; i < m_context.certificates.size(); ++i) { + auto& cert = m_context.certificates[i]; + if (wildcard_matches(host, cert.subject)) + return i; + for (auto& san : cert.SAN) { + if (wildcard_matches(host, san)) + return i; + } + } + + return {}; +} + +TLSv12::TLSv12(Core::Object* parent, Version version) + : Core::Socket(Core::Socket::Type::TCP, parent) +{ + m_context.version = version; + m_context.is_server = false; + m_context.tls_buffer = ByteBuffer::create_uninitialized(0); +#ifdef SOCK_NONBLOCK + int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); +#else + int fd = socket(AF_INET, SOCK_STREAM, 0); + int option = 1; + ioctl(fd, FIONBIO, &option); +#endif + if (fd < 0) { + set_error(errno); + } else { + set_fd(fd); + set_mode(IODevice::ReadWrite); + set_error(0); + } +} + +bool TLSv12::add_client_key(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes rsa_key) // FIXME: This should not be bound to RSA +{ + if (certificate_pem_buffer.is_empty() || rsa_key.is_empty()) { + return true; + } + auto decoded_certificate = Crypto::decode_pem(certificate_pem_buffer, 0); + if (decoded_certificate.is_empty()) { + dbgln("Certificate not PEM"); + return false; + } + + auto maybe_certificate = parse_asn1(decoded_certificate); + if (!maybe_certificate.has_value()) { + dbgln("Invalid certificate"); + return false; + } + + Crypto::PK::RSA rsa(rsa_key); + auto certificate = maybe_certificate.value(); + certificate.private_key = rsa.private_key(); + + return add_client_key(certificate); +} + +AK::Singleton<DefaultRootCACertificates> DefaultRootCACertificates::s_the; +DefaultRootCACertificates::DefaultRootCACertificates() +{ + // FIXME: This might not be the best format, find a better way to represent CA certificates. + auto config = Core::ConfigFile::get_for_system("ca_certs"); + for (auto& entity : config->groups()) { + Certificate cert; + cert.subject = entity; + cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity); + cert.country = config->read_entry(entity, "country"); + m_ca_certificates.append(move(cert)); + } +} + +} diff --git a/Userland/Libraries/LibTLS/TLSv12.h b/Userland/Libraries/LibTLS/TLSv12.h new file mode 100644 index 0000000000..219b9f55d4 --- /dev/null +++ b/Userland/Libraries/LibTLS/TLSv12.h @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Certificate.h" +#include <AK/IPv4Address.h> +#include <AK/WeakPtr.h> +#include <LibCore/Notifier.h> +#include <LibCore/Socket.h> +#include <LibCore/TCPSocket.h> +#include <LibCrypto/Authentication/HMAC.h> +#include <LibCrypto/BigInt/UnsignedBigInteger.h> +#include <LibCrypto/Cipher/AES.h> +#include <LibCrypto/Hash/HashManager.h> +#include <LibCrypto/PK/RSA.h> +#include <LibTLS/TLSPacketBuilder.h> + +namespace TLS { + +inline void print_buffer(ReadonlyBytes buffer) +{ + for (size_t i { 0 }; i < buffer.size(); ++i) + dbgprintf("%02x ", buffer[i]); + dbgprintf("\n"); +} + +inline void print_buffer(const ByteBuffer& buffer) +{ + print_buffer(buffer.bytes()); +} + +inline void print_buffer(const u8* buffer, size_t size) +{ + print_buffer(ReadonlyBytes { buffer, size }); +} + +class Socket; + +enum class CipherSuite { + Invalid = 0, + AES_128_GCM_SHA256 = 0x1301, + AES_256_GCM_SHA384 = 0x1302, + AES_128_CCM_SHA256 = 0x1304, + AES_128_CCM_8_SHA256 = 0x1305, + + // We support these + RSA_WITH_AES_128_CBC_SHA = 0x002F, + RSA_WITH_AES_256_CBC_SHA = 0x0035, + RSA_WITH_AES_128_CBC_SHA256 = 0x003C, + RSA_WITH_AES_256_CBC_SHA256 = 0x003D, + // TODO + RSA_WITH_AES_128_GCM_SHA256 = 0x009C, + RSA_WITH_AES_256_GCM_SHA384 = 0x009D, +}; + +#define ENUMERATE_ALERT_DESCRIPTIONS \ + ENUMERATE_ALERT_DESCRIPTION(CloseNotify, 0) \ + ENUMERATE_ALERT_DESCRIPTION(UnexpectedMessage, 10) \ + ENUMERATE_ALERT_DESCRIPTION(BadRecordMAC, 20) \ + ENUMERATE_ALERT_DESCRIPTION(DecryptionFailed, 21) \ + ENUMERATE_ALERT_DESCRIPTION(RecordOverflow, 22) \ + ENUMERATE_ALERT_DESCRIPTION(DecompressionFailure, 30) \ + ENUMERATE_ALERT_DESCRIPTION(HandshakeFailure, 40) \ + ENUMERATE_ALERT_DESCRIPTION(NoCertificate, 41) \ + ENUMERATE_ALERT_DESCRIPTION(BadCertificate, 42) \ + ENUMERATE_ALERT_DESCRIPTION(UnsupportedCertificate, 43) \ + ENUMERATE_ALERT_DESCRIPTION(CertificateRevoked, 44) \ + ENUMERATE_ALERT_DESCRIPTION(CertificateExpired, 45) \ + ENUMERATE_ALERT_DESCRIPTION(CertificateUnknown, 46) \ + ENUMERATE_ALERT_DESCRIPTION(IllegalParameter, 47) \ + ENUMERATE_ALERT_DESCRIPTION(UnknownCA, 48) \ + ENUMERATE_ALERT_DESCRIPTION(AccessDenied, 49) \ + ENUMERATE_ALERT_DESCRIPTION(DecodeError, 50) \ + ENUMERATE_ALERT_DESCRIPTION(DecryptError, 51) \ + ENUMERATE_ALERT_DESCRIPTION(ExportRestriction, 60) \ + ENUMERATE_ALERT_DESCRIPTION(ProtocolVersion, 70) \ + ENUMERATE_ALERT_DESCRIPTION(InsufficientSecurity, 71) \ + ENUMERATE_ALERT_DESCRIPTION(InternalError, 80) \ + ENUMERATE_ALERT_DESCRIPTION(InappropriateFallback, 86) \ + ENUMERATE_ALERT_DESCRIPTION(UserCanceled, 90) \ + ENUMERATE_ALERT_DESCRIPTION(NoRenegotiation, 100) \ + ENUMERATE_ALERT_DESCRIPTION(UnsupportedExtension, 110) \ + ENUMERATE_ALERT_DESCRIPTION(NoError, 255) + +enum class AlertDescription : u8 { +#define ENUMERATE_ALERT_DESCRIPTION(name, value) name = value, + ENUMERATE_ALERT_DESCRIPTIONS +#undef ENUMERATE_ALERT_DESCRIPTION +}; + +constexpr static const char* alert_name(AlertDescription descriptor) +{ +#define ENUMERATE_ALERT_DESCRIPTION(name, value) \ + case AlertDescription::name: \ + return #name; + + switch (descriptor) { + ENUMERATE_ALERT_DESCRIPTIONS + } + + return "Unknown"; +#undef ENUMERATE_ALERT_DESCRIPTION +} + +enum class Error : i8 { + NoError = 0, + UnknownError = -1, + BrokenPacket = -2, + NotUnderstood = -3, + NoCommonCipher = -5, + UnexpectedMessage = -6, + CloseConnection = -7, + CompressionNotSupported = -8, + NotVerified = -9, + NotSafe = -10, + IntegrityCheckFailed = -11, + ErrorAlert = -12, + BrokenConnection = -13, + BadCertificate = -14, + UnsupportedCertificate = -15, + NoRenegotiation = -16, + FeatureNotSupported = -17, + DecryptionFailed = -20, + NeedMoreData = -21, + TimedOut = -22, +}; + +enum class AlertLevel : u8 { + Warning = 0x01, + Critical = 0x02 +}; + +enum HandshakeType { + HelloRequest = 0x00, + ClientHello = 0x01, + ServerHello = 0x02, + HelloVerifyRequest = 0x03, + CertificateMessage = 0x0b, + ServerKeyExchange = 0x0c, + CertificateRequest = 0x0d, + ServerHelloDone = 0x0e, + CertificateVerify = 0x0f, + ClientKeyExchange = 0x10, + Finished = 0x14 +}; + +enum class HandshakeExtension : u16 { + ServerName = 0x00, + ApplicationLayerProtocolNegotiation = 0x10, + SignatureAlgorithms = 0x0d, +}; + +enum class WritePacketStage { + Initial = 0, + ClientHandshake = 1, + ServerHandshake = 2, + Finished = 3, +}; + +enum class ConnectionStatus { + Disconnected, + Negotiating, + KeyExchange, + Renegotiating, + Established, +}; + +enum ClientVerificationStaus { + Verified, + VerificationNeeded, +}; + +struct Context { + String to_string() const; + bool verify() const; + bool verify_chain() const; + + static void print_file(const StringView& fname); + + u8 remote_random[32]; + // To be predictable + u8 local_random[32]; + u8 session_id[32]; + u8 session_id_size { 0 }; + CipherSuite cipher; + Version version; + bool is_server { false }; + Vector<Certificate> certificates; + Certificate private_key; + Vector<Certificate> client_certificates; + ByteBuffer master_key; + ByteBuffer premaster_key; + u8 cipher_spec_set { 0 }; + struct { + int created { 0 }; + u8 remote_mac[32]; + u8 local_mac[32]; + u8 local_iv[16]; + u8 remote_iv[16]; + u8 local_aead_iv[4]; + u8 remote_aead_iv[4]; + } crypto; + + Crypto::Hash::Manager handshake_hash; + + ByteBuffer message_buffer; + u64 remote_sequence_number { 0 }; + u64 local_sequence_number { 0 }; + + ConnectionStatus connection_status { ConnectionStatus::Disconnected }; + u8 critical_error { 0 }; + Error error_code { Error::NoError }; + + ByteBuffer tls_buffer; + + ByteBuffer application_buffer; + + bool is_child { false }; + + String SNI; // I hate your existence + + u8 request_client_certificate { 0 }; + + ByteBuffer cached_handshake; + + ClientVerificationStaus client_verified { Verified }; + + bool connection_finished { false }; + + // message flags + u8 handshake_messages[11] { 0 }; + ByteBuffer user_data; + Vector<Certificate> root_ceritificates; + + Vector<String> alpn; + StringView negotiated_alpn; + + size_t send_retries { 0 }; + + time_t handshake_initiation_timestamp { 0 }; +}; + +class TLSv12 : public Core::Socket { + C_OBJECT(TLSv12) +public: + ByteBuffer& write_buffer() { return m_context.tls_buffer; } + bool is_established() const { return m_context.connection_status == ConnectionStatus::Established; } + virtual bool connect(const String&, int) override; + + void set_sni(const StringView& sni) + { + if (m_context.is_server || m_context.critical_error || m_context.connection_status != ConnectionStatus::Disconnected) { + dbgln("invalid state for set_sni"); + return; + } + m_context.SNI = sni; + } + + Optional<Certificate> parse_asn1(ReadonlyBytes, bool client_cert = false) const; + bool load_certificates(ReadonlyBytes pem_buffer); + bool load_private_key(ReadonlyBytes pem_buffer); + + void set_root_certificates(Vector<Certificate>); + + bool add_client_key(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes key_pem_buffer); + bool add_client_key(Certificate certificate) + { + m_context.client_certificates.append(move(certificate)); + return true; + } + + ByteBuffer finish_build(); + + const StringView& alpn() const { return m_context.negotiated_alpn; } + void add_alpn(const StringView& alpn); + bool has_alpn(const StringView& alpn) const; + + bool supports_cipher(CipherSuite suite) const + { + return suite == CipherSuite::RSA_WITH_AES_128_CBC_SHA256 + || suite == CipherSuite::RSA_WITH_AES_256_CBC_SHA256 + || suite == CipherSuite::RSA_WITH_AES_128_CBC_SHA + || suite == CipherSuite::RSA_WITH_AES_256_CBC_SHA + || suite == CipherSuite::RSA_WITH_AES_128_GCM_SHA256; + } + + bool supports_version(Version v) const + { + return v == Version::V12; + } + + Optional<ByteBuffer> read(); + ByteBuffer read(size_t max_size); + + bool write(ReadonlyBytes); + void alert(AlertLevel, AlertDescription); + + bool can_read_line() const { return m_context.application_buffer.size() && memchr(m_context.application_buffer.data(), '\n', m_context.application_buffer.size()); } + bool can_read() const { return m_context.application_buffer.size() > 0; } + String read_line(size_t max_size); + + Function<void(TLSv12&)> on_tls_ready_to_read; + Function<void(TLSv12&)> on_tls_ready_to_write; + Function<void(AlertDescription)> on_tls_error; + Function<void()> on_tls_connected; + Function<void()> on_tls_finished; + Function<void(TLSv12&)> on_tls_certificate_request; + +private: + explicit TLSv12(Core::Object* parent, Version version = Version::V12); + + virtual bool common_connect(const struct sockaddr*, socklen_t) override; + + void consume(ReadonlyBytes record); + + ByteBuffer hmac_message(const ReadonlyBytes& buf, const Optional<ReadonlyBytes> buf2, size_t mac_length, bool local = false); + void ensure_hmac(size_t digest_size, bool local); + + void update_packet(ByteBuffer& packet); + void update_hash(ReadonlyBytes in); + + void write_packet(ByteBuffer& packet); + + ByteBuffer build_client_key_exchange(); + ByteBuffer build_server_key_exchange(); + + ByteBuffer build_hello(); + ByteBuffer build_finished(); + ByteBuffer build_certificate(); + ByteBuffer build_done(); + ByteBuffer build_alert(bool critical, u8 code); + ByteBuffer build_change_cipher_spec(); + ByteBuffer build_verify_request(); + void build_random(PacketBuilder&); + + bool flush(); + void write_into_socket(); + void read_from_socket(); + + bool check_connection_state(bool read); + + ssize_t handle_hello(ReadonlyBytes, WritePacketStage&); + ssize_t handle_finished(ReadonlyBytes, WritePacketStage&); + ssize_t handle_certificate(ReadonlyBytes); + ssize_t handle_server_key_exchange(ReadonlyBytes); + ssize_t handle_server_hello_done(ReadonlyBytes); + ssize_t handle_verify(ReadonlyBytes); + ssize_t handle_payload(ReadonlyBytes); + ssize_t handle_message(ReadonlyBytes); + ssize_t handle_random(ReadonlyBytes); + + size_t asn1_length(ReadonlyBytes, size_t* octets); + + void pseudorandom_function(Bytes output, ReadonlyBytes secret, const u8* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b); + + size_t key_length() const + { + switch (m_context.cipher) { + case CipherSuite::AES_128_CCM_8_SHA256: + case CipherSuite::AES_128_CCM_SHA256: + case CipherSuite::AES_128_GCM_SHA256: + case CipherSuite::Invalid: + case CipherSuite::RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite::RSA_WITH_AES_128_CBC_SHA: + case CipherSuite::RSA_WITH_AES_128_GCM_SHA256: + default: + return 128 / 8; + case CipherSuite::AES_256_GCM_SHA384: + case CipherSuite::RSA_WITH_AES_256_CBC_SHA: + case CipherSuite::RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite::RSA_WITH_AES_256_GCM_SHA384: + return 256 / 8; + } + } + size_t mac_length() const + { + switch (m_context.cipher) { + case CipherSuite::RSA_WITH_AES_128_CBC_SHA: + case CipherSuite::RSA_WITH_AES_256_CBC_SHA: + return Crypto::Hash::SHA1::digest_size(); + case CipherSuite::AES_256_GCM_SHA384: + case CipherSuite::RSA_WITH_AES_256_GCM_SHA384: + return Crypto::Hash::SHA512::digest_size(); + case CipherSuite::AES_128_CCM_8_SHA256: + case CipherSuite::AES_128_CCM_SHA256: + case CipherSuite::AES_128_GCM_SHA256: + case CipherSuite::Invalid: + case CipherSuite::RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite::RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite::RSA_WITH_AES_256_CBC_SHA256: + default: + return Crypto::Hash::SHA256::digest_size(); + } + } + size_t iv_length() const + { + switch (m_context.cipher) { + case CipherSuite::AES_128_CCM_8_SHA256: + case CipherSuite::AES_128_CCM_SHA256: + case CipherSuite::Invalid: + case CipherSuite::RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite::RSA_WITH_AES_128_CBC_SHA: + case CipherSuite::RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite::RSA_WITH_AES_256_CBC_SHA: + default: + return 16; + case CipherSuite::AES_128_GCM_SHA256: + case CipherSuite::AES_256_GCM_SHA384: + case CipherSuite::RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite::RSA_WITH_AES_256_GCM_SHA384: + return 8; // 4 bytes of fixed IV, 8 random (nonce) bytes, 4 bytes for counter + // GCM specifically asks us to transmit only the nonce, the counter is zero + // and the fixed IV is derived from the premaster key. + } + } + + bool is_aead() const + { + switch (m_context.cipher) { + case CipherSuite::AES_128_GCM_SHA256: + case CipherSuite::AES_256_GCM_SHA384: + case CipherSuite::RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite::RSA_WITH_AES_256_GCM_SHA384: + return true; + default: + return false; + } + } + + bool expand_key(); + + bool compute_master_secret(size_t length); + + Optional<size_t> verify_chain_and_get_matching_certificate(const StringView& host) const; + + void try_disambiguate_error() const; + + Context m_context; + + OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_local; + OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_remote; + + struct { + OwnPtr<Crypto::Cipher::AESCipher::CBCMode> cbc; + OwnPtr<Crypto::Cipher::AESCipher::GCMMode> gcm; + } m_aes_local, m_aes_remote; + + bool m_has_scheduled_write_flush { false }; + i32 m_max_wait_time_for_handshake_in_seconds { 10 }; + + RefPtr<Core::Timer> m_handshake_timeout_timer; +}; + +namespace Constants { +constexpr static const u32 version_id[] { 1, 1, 1, 0 }; +constexpr static const u32 pk_id[] { 1, 1, 7, 0 }; +constexpr static const u32 serial_id[] { 1, 1, 2, 1, 0 }; +constexpr static const u32 issurer_id[] { 1, 1, 4, 0 }; +constexpr static const u32 owner_id[] { 1, 1, 6, 0 }; +constexpr static const u32 validity_id[] { 1, 1, 5, 0 }; +constexpr static const u32 algorithm_id[] { 1, 1, 3, 0 }; +constexpr static const u32 sign_id[] { 1, 3, 2, 1, 0 }; +constexpr static const u32 priv_id[] { 1, 4, 0 }; +constexpr static const u32 priv_der_id[] { 1, 3, 1, 0 }; +constexpr static const u32 ecc_priv_id[] { 1, 2, 0 }; + +constexpr static const u8 country_oid[] { 0x55, 0x04, 0x06, 0x00 }; +constexpr static const u8 state_oid[] { 0x55, 0x04, 0x08, 0x00 }; +constexpr static const u8 location_oid[] { 0x55, 0x04, 0x07, 0x00 }; +constexpr static const u8 entity_oid[] { 0x55, 0x04, 0x0A, 0x00 }; +constexpr static const u8 subject_oid[] { 0x55, 0x04, 0x03, 0x00 }; +constexpr static const u8 unit_oid[] { 0x55, 0x04, 0x0B, 0x00 }; +constexpr static const u8 san_oid[] { 0x55, 0x1D, 0x11, 0x00 }; +constexpr static const u8 ocsp_oid[] { 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x00 }; + +static constexpr const u8 RSA_SIGN_RSA_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x00 }; +static constexpr const u8 RSA_SIGN_MD5_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04, 0x00 }; +static constexpr const u8 RSA_SIGN_SHA1_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x00 }; +static constexpr const u8 RSA_SIGN_SHA256_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x00 }; +static constexpr const u8 RSA_SIGN_SHA384_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x00 }; +static constexpr const u8 RSA_SIGN_SHA512_OID[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d, 0x00 }; + +} + +} |