From: Christos Tsantilas Date: Tue, 29 Mar 2016 19:17:34 +0000 (+0300) Subject: Improve TLS/SSL parsing code in Handshale.cc and use it inside bio.cc for client X-Git-Tag: SQUID_4_0_11~29^2~33 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=21530947cd4a8eb4361e4dd0c4dbfc5ca487da3d;p=thirdparty%2Fsquid.git Improve TLS/SSL parsing code in Handshale.cc and use it inside bio.cc for client and server messages - full implementation for TLS and SSLv2 parsers inside Handshake.cc/h files - remove parsing code from bio.cc - Store parsed info in new Security::TlsDetails struct and remove the Ssl::sslFeatures class - improve SSLv2 parsing code. --- diff --git a/src/client_side.cc b/src/client_side.cc index 5203897df4..c0de5520ee 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -3176,10 +3176,10 @@ clientPeekAndSpliceSSL(int fd, void *data) if (bio->gotHello()) { if (conn->serverBump()) { - Ssl::Bio::sslFeatures const &features = bio->receivedHelloFeatures(); - if (!features.serverName.isEmpty()) { - conn->serverBump()->clientSni = features.serverName; - conn->resetSslCommonName(features.serverName.c_str()); + Security::TlsDetails::Pointer const &details = bio->receivedHelloDetails(); + if (!details->serverName.isEmpty()) { + conn->serverBump()->clientSni = details->serverName; + conn->resetSslCommonName(details->serverName.c_str()); } } diff --git a/src/security/Handshake.cc b/src/security/Handshake.cc index f0e05ab271..747d0bd9ac 100644 --- a/src/security/Handshake.cc +++ b/src/security/Handshake.cc @@ -21,22 +21,14 @@ Security::ProtocolVersion::ProtocolVersion(BinaryTokenizer &tk): { } -Security::ProtocolVersion::ProtocolVersion(uint8_t maj, uint8_t min): - vMajor(maj), - vMinor(min) -{ -} - Security::TLSPlaintext::TLSPlaintext(BinaryTokenizer &tk): FieldGroup(tk, "TLSPlaintext"), type(tk.uint8(".type")), - version((type & 0x80) ? ProtocolVersion(2, 0): ProtocolVersion(tk)) + version(tk), + length(tk.uint16(".length")) { - if (type & 0x80){ // V2 compatible protocol - length = tk.uint8(".length"); - } else { //TLS protocol - length = tk.uint16(".length"); - } + Must(version.vMajor == 3 && version.vMinor <= 3); + Must(type >= ctChangeCipherSpec && type <= ctApplicationData); fragment = tk.area(length, ".fragment"); commit(tk); } @@ -91,6 +83,20 @@ Security::Extension::Extension(BinaryTokenizer &tk): commit(tk); } +Security::SSL2Record::SSL2Record(BinaryTokenizer &tk): + FieldGroup(tk, "SSL2Record") +{ + uint16_t head = tk.uint16(".head(Record+Length)"); + length = head & 0x7FFF; + type = tk.uint8(".type"); + if ((head & 0x8000) == 0 || length == 0 || type != 0x01) + throw TexcHere("Not an SSLv2 message"); + version = 0x02; + // The remained message has length of length-sizeof(type)=(length-1); + fragment = tk.area(length - 1, ".fragment"); + commit(tk); +} + //The SNI extension has the type 0 (extType == 0) // RFC6066 sections 3, 10.2 // The two first bytes indicates the length of the SNI data @@ -102,10 +108,10 @@ Security::SniExtension::SniExtension(BinaryTokenizer &tk): type(tk.uint8(".type")) { if (type == 0) { - P16String aName(tk, "server name"); + P16String aName(tk, ".serverName"); serverName = aName.body; } else - tk.skip(listLength - 1, "list without list type"); + tk.skip(listLength - 1, ".unknownType"); commit(tk); } @@ -138,10 +144,37 @@ operator <<(std::ostream &os, const DebugFrame &frame) return os << frame.size << "-byte type-" << frame.type << ' ' << frame.name; } +bool +Security::HandshakeParser::parseRecordVersion2Try() +{ + try { + const SSL2Record record(tkRecords); + details->tlsVersion = record.version; + parseVersion2HandshakeMessage(record.fragment); + state = atHelloReceived; + parseDone = true; + return true; + } catch (const BinaryTokenizer::InsufficientInput &) { + throw BinaryTokenizer::InsufficientInput(); + } catch (const std::exception &ex) { + debugs(83, 5, "fallback to the TLS records parser: " << ex.what()); + useTlsParser = true; + tkRecords.rollback(); + return false; + } + return false; +} + /// parses a single TLS Record Layer frame void Security::HandshakeParser::parseRecord() { + if (details == NULL) + details = new TlsDetails; + + if (!useTlsParser && parseRecordVersion2Try()) + return; + const TLSPlaintext record(tkRecords); Must(record.length <= (1 << 14)); // RFC 5246: length MUST NOT exceed 2^14 @@ -149,10 +182,7 @@ Security::HandshakeParser::parseRecord() // RFC 5246: MUST NOT send zero-length [non-application] fragments Must(record.length || record.type == ContentType::ctApplicationData); - if (details == NULL) { - details = new TlsDetails; - details->tlsVersion = (record.version.vMajor & 0xFF) << 8 | (record.version.vMinor & 0xFF); - } + details->tlsVersion = (record.version.vMajor & 0xFF) << 8 | (record.version.vMinor & 0xFF); if (currentContentType != record.type) { Must(tkMessages.atEnd()); // no currentContentType leftovers @@ -186,11 +216,6 @@ Security::HandshakeParser::parseMessages() case ContentType::ctApplicationData: parseApplicationDataMessage(); continue; - case ContentType::ctVersion2: { - SBuf raw; //Should fixed to body after record - parseVersion2HandshakeMessage(raw); - continue; - } } skipMessage("unknown ContentType msg"); } @@ -269,12 +294,16 @@ Security::HandshakeParser::parseVersion2HandshakeMessage(const SBuf &raw) Must(details); details->tlsSupportedVersion = tkHsk.uint16("tlsSupportedVersion"); - tkHsk.commit(); - P16String ciphers(tkHsk, "Ciphers list"); - // TODO: retrieve ciphers list - P16String session(tkHsk, "Session ID"); - details->sessionId = session.body; - P16String challenge(tkHsk, "Challenge"); + uint16_t ciphersLen = tkHsk.uint16(".cipherSpecLength"); + uint16_t sessionIdLen = tkHsk.uint16(".sessionIdLength"); + uint16_t challengeLen = tkHsk.uint16(".challengeLength"); + + SBuf ciphers = tkHsk.area(ciphersLen, "Ciphers list"); + parseV23Ciphers(ciphers); + details->sessionId = tkHsk.area(sessionIdLen, "Session Id"); + + // We do not actually need it: + SBuf challenge = tkHsk.area(challengeLen, "Challenge"); } void @@ -288,11 +317,13 @@ Security::HandshakeParser::parseClientHelloHandshakeMessage(const SBuf &raw) P8String session(tkHsk, "Session ID"); details->sessionId = session.body; P16String ciphers(tkHsk, "Ciphers list"); - // TODO: retrieve ciphers list + parseCiphers(ciphers.body); P8String compression(tkHsk, "Compression methods"); details->compressMethod = compression.length > 0 ? 1 : 0; // Only deflate supported here. - P16String extensions(tkHsk, "Extensions List"); - parseExtensions(extensions.body); + if (!tkHsk.atEnd()) { //Then we have extensions + P16String extensions(tkHsk, "Extensions List"); + parseExtensions(extensions.body); + } } void diff --git a/src/security/Handshake.h b/src/security/Handshake.h index 1f3a2339fa..5041d83aa1 100644 --- a/src/security/Handshake.h +++ b/src/security/Handshake.h @@ -33,7 +33,6 @@ public: /// TLS Record Layer's content types from RFC 5246 Section 6.2.1 enum ContentType { - ctVersion2 = 128, ctChangeCipherSpec = 20, ctAlert = 21, ctHandshake = 22, @@ -44,7 +43,6 @@ enum ContentType { struct ProtocolVersion { explicit ProtocolVersion(BinaryTokenizer &tk); - ProtocolVersion(uint8_t, uint8_t); // the "v" prefix works around environments that #define major and minor uint8_t vMajor; @@ -62,6 +60,15 @@ struct TLSPlaintext: public FieldGroup SBuf fragment; ///< exactly length bytes }; +struct SSL2Record: public FieldGroup +{ + explicit SSL2Record(BinaryTokenizer &tk); + uint16_t version; + uint16_t length; + uint8_t type; + SBuf fragment; +}; + /// TLS Handshake protocol's handshake types from RFC 5246 Section 7.4 enum HandshakeType { hskClientHello = 1, @@ -142,6 +149,9 @@ public: typedef RefCount Pointer; TlsDetails(); + /// Prints to os stream a human readable form of TlsDetails object + std::ostream & print(std::ostream &os) const; + int tlsVersion; ///< The TLS hello message version int tlsSupportedVersion; ///< The requested/used TLS version int compressMethod; ///< The requested/used compressed method @@ -158,13 +168,19 @@ public: std::list extensions; }; +inline +std::ostream &operator <<(std::ostream &os, Security::TlsDetails const &details) +{ + return details.print(os); +} + /// Incremental SSL Handshake parser. class HandshakeParser { public: /// The parsing states typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived, atCertificatesReceived, atHelloDoneReceived, atNstReceived, atCcsReceived, atFinishReceived} ParserState; - HandshakeParser(): state(atHelloNone), ressumingSession(false), parseDone(false), parseError(false), currentContentType(0), unParsedContent(0), parsingPos(0), currentMsg(0), currentMsgSize(0), certificatesMsgPos(0), certificatesMsgSize(0) {} + HandshakeParser(): state(atHelloNone), ressumingSession(false), parseDone(false), parseError(false), currentContentType(0), unParsedContent(0), parsingPos(0), currentMsg(0), currentMsgSize(0), certificatesMsgPos(0), certificatesMsgSize(0), useTlsParser(false) {} /// Parses the initial sequence of raw bytes sent by the SSL server. /// Returns true upon successful completion (HelloDone or Finished received). @@ -176,6 +192,7 @@ public: /// Otherwise, returns false (and sets parseError to true on errors). bool parseClientHello(const SBuf &data); + TlsDetails::Pointer details; #if USE_OPENSSL Ssl::X509_STACK_Pointer serverCertificates; ///< parsed certificates chain #endif @@ -209,6 +226,7 @@ private: void parseApplicationDataMessage(); void skipMessage(const char *msgType); + bool parseRecordVersion2Try(); void parseVersion2HandshakeMessage(const SBuf &raw); void parseClientHelloHandshakeMessage(const SBuf &raw); void parseServerHelloHandshakeMessage(const SBuf &raw); @@ -228,7 +246,7 @@ private: BinaryTokenizer tkRecords; // TLS record layer (parsing uninterpreted data) BinaryTokenizer tkMessages; // TLS message layer (parsing fragments) - TlsDetails::Pointer details; + bool useTlsParser; // Whether to use TLS parser or a V2 compatible parser }; } diff --git a/src/security/NegotiationHistory.cc b/src/security/NegotiationHistory.cc index 5bad38247d..d990610470 100644 --- a/src/security/NegotiationHistory.cc +++ b/src/security/NegotiationHistory.cc @@ -65,13 +65,14 @@ Security::NegotiationHistory::fillWith(Security::SessionPtr ssl) Ssl::Bio *bio = static_cast(b->ptr); if (::Config.onoff.logTlsServerHelloDetails) { - if (Ssl::ServerBio *srvBio = dynamic_cast(bio)) - srvBio->extractHelloFeatures(); + //PRobably move this if inside HandhakeParser + // if (Ssl::ServerBio *srvBio = dynamic_cast(bio)) + // srvBio->extractHelloFeatures(); } - const Ssl::Bio::sslFeatures &features = bio->receivedHelloFeatures(); - helloVersion_ = features.sslHelloVersion; - supportedVersion_ = features.sslVersion; + const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails(); + helloVersion_ = details->tlsVersion; + supportedVersion_ = details->tlsSupportedVersion; debugs(83, 5, "SSL connection info on FD " << bio->fd() << " SSL version " << version_ << diff --git a/src/ssl/PeekingPeerConnector.cc b/src/ssl/PeekingPeerConnector.cc index 88ae4ed4d4..89ec1a1dcc 100644 --- a/src/ssl/PeekingPeerConnector.cc +++ b/src/ssl/PeekingPeerConnector.cc @@ -155,9 +155,9 @@ Ssl::PeekingPeerConnector::initializeSsl() if (auto clientSsl = fd_table[clientConn->fd].ssl.get()) { BIO *b = SSL_get_rbio(clientSsl); cltBio = static_cast(b->ptr); - const Ssl::Bio::sslFeatures &features = cltBio->receivedHelloFeatures(); - if (!features.serverName.isEmpty()) - hostName = new SBuf(features.serverName); + const Security::TlsDetails::Pointer &details = cltBio->receivedHelloDetails(); + if (details != NULL && !details->serverName.isEmpty()) + hostName = new SBuf(details->serverName); } if (!hostName) { @@ -175,15 +175,15 @@ Ssl::PeekingPeerConnector::initializeSsl() Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2); if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) { assert(cltBio); - const Ssl::Bio::sslFeatures &features = cltBio->receivedHelloFeatures(); - if (features.sslVersion != -1) { - features.applyToSSL(ssl, csd->sslBumpMode); + const Security::TlsDetails::Pointer &details = cltBio->receivedHelloDetails(); + if (details->tlsVersion != -1) { + applyTlsDetailsToSSL(ssl, details, csd->sslBumpMode); // Should we allow it for all protocols? - if (features.sslVersion >= 3) { + if (details->tlsVersion >= 3) { BIO *b = SSL_get_rbio(ssl); Ssl::ServerBio *srvBio = static_cast(b->ptr); // Inherite client features, like SSL version, SNI and other - srvBio->setClientFeatures(features); + srvBio->setClientFeatures(details, cltBio->rBufData()); srvBio->recordInput(true); srvBio->mode(csd->sslBumpMode); } diff --git a/src/ssl/bio.cc b/src/ssl/bio.cc index 9c2b22ad79..ef242d0cfa 100644 --- a/src/ssl/bio.cc +++ b/src/ssl/bio.cc @@ -195,35 +195,15 @@ Ssl::ClientBio::write(const char *buf, int size, BIO *table) int Ssl::ClientBio::read(char *buf, int size, BIO *table) { - if (helloState < atHelloReceived) { + if (!parser_.parseDone) { int bytes = readAndBuffer(table, "TLS client Hello"); if (bytes <= 0) return bytes; - } - - if (helloState == atHelloNone) { - helloSize = receivedHelloFeatures_.parseMsgHead(rbuf); - if (helloSize == 0) { - // Not enough bytes to get hello message size - BIO_set_retry_read(table); - return -1; - } else if (helloSize < 0) { - wrongProtocol = true; + if (!parser_.parseClientHello(rbuf)) { + if (!parser_.parseError) + BIO_set_retry_read(table); return -1; } - - helloState = atHelloStarted; //Next state - } - - if (helloState == atHelloStarted) { - debugs(83, 7, "SSL Header: " << Raw(nullptr, rbuf.rawContent(), rbuf.length()).hex()); - - if (helloSize > (int)rbuf.length()) { - BIO_set_retry_read(table); - return -1; - } - receivedHelloFeatures_.get(rbuf); - helloState = atHelloReceived; } if (holdRead_) { @@ -232,7 +212,7 @@ Ssl::ClientBio::read(char *buf, int size, BIO *table) return -1; } - if (helloState == atHelloReceived) { + if (parser_.parseDone) { if (!rbuf.isEmpty()) { int bytes = (size <= (int)rbuf.length() ? size : rbuf.length()); memcpy(buf, rbuf.rawContent(), bytes); @@ -252,9 +232,10 @@ Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int ret) } void -Ssl::ServerBio::setClientFeatures(const Ssl::Bio::sslFeatures &features) +Ssl::ServerBio::setClientFeatures(Security::TlsDetails::Pointer const &details, SBuf const &aHello) { - clientFeatures = features; + clientTlsDetails = details; + clientHelloMessage = aHello; }; int @@ -312,7 +293,7 @@ Ssl::ServerBio::read(char *buf, int size, BIO *table) // This is mostly possible in the cases where the web client uses openSSL // library similar with this one used by squid. static bool -adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features) +adjustSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, SBuf &helloMessage) { #if SQUID_USE_OPENSSL_HELLO_OVERWRITE_HACK if (!ssl->s3) { @@ -323,9 +304,9 @@ adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features) // If the client supports compression but our context does not support // we can not adjust. #if !defined(OPENSSL_NO_COMP) - const bool requireCompression = (features.compressMethod && ssl->ctx->comp_methods == NULL); + const bool requireCompression = (details->compressMethod && ssl->ctx->comp_methods == NULL); #else - const bool requireCompression = features.compressMethod; + const bool requireCompression = details->compressMethod; #endif if (requireCompression) { debugs(83, 5, "Client Hello Data supports compression, but we do not!"); @@ -333,37 +314,31 @@ adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features) } // Check ciphers list - size_t token = 0; - size_t end = 0; - while (token != std::string::npos) { - end = features.clientRequestedCiphers.find(':',token); - std::string cipher; - cipher.assign(features.clientRequestedCiphers, token, end - token); - token = (end != std::string::npos ? end + 1 : std::string::npos); + for (auto cipherId: details->ciphers) { bool found = false; STACK_OF(SSL_CIPHER) *cipher_stack = SSL_get_ciphers(ssl); for (int i = 0; i < sk_SSL_CIPHER_num(cipher_stack); i++) { SSL_CIPHER *c = sk_SSL_CIPHER_value(cipher_stack, i); - const char *cname = SSL_CIPHER_get_name(c); - if (cipher.compare(cname)) { + const long cid = SSL_CIPHER_get_id(c); + if (cipherId == (0xFFFF & cid)) { found = true; break; } } if (!found) { - debugs(83, 5, "Client Hello Data supports cipher '"<< cipher <<"' but we do not support it!"); + debugs(83, 5, "Client Hello Data supports cipher '"<< cipherId <<"' but we do not support it!"); return false; } } #if !defined(SSL_TLSEXT_HB_ENABLED) - if (features.doHeartBeats) { + if (details->doHeartBeats) { debugs(83, 5, "Client Hello Data supports HeartBeats but we do not support!"); return false; } #endif - for (std::list::iterator it = features.extensions.begin(); it != features.extensions.end(); ++it) { + for (std::list::iterator it = details->extensions.begin(); it != details->extensions.end(); ++it) { static int supportedExtensions[] = { #if defined(TLSEXT_TYPE_server_name) TLSEXT_TYPE_server_name, @@ -411,19 +386,20 @@ adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features) } SSL3_BUFFER *wb=&(ssl->s3->wbuf); - if (wb->len < (size_t)features.helloMessage.length()) + if (wb->len < (size_t)helloMessage.length()) return false; debugs(83, 5, "OpenSSL SSL struct will be adjusted to mimic client hello data!"); //Adjust ssl structure data. // We need to fix the random in SSL struct: - memcpy(ssl->s3->client_random, features.client_random, SSL3_RANDOM_SIZE); - memcpy(wb->buf, features.helloMessage.rawContent(), features.helloMessage.length()); - wb->left = features.helloMessage.length(); + if (details->clientRandom.length() == SSL3_RANDOM_SIZE) + memcpy(ssl->s3->client_random, details->clientRandom.c_str(), SSL3_RANDOM_SIZE); + memcpy(wb->buf, helloMessage.rawContent(), helloMessage.length()); + wb->left = helloMessage.length(); - size_t mainHelloSize = features.helloMessage.length() - 5; - const char *mainHello = features.helloMessage.rawContent() + 5; + size_t mainHelloSize = helloMessage.length() - 5; + const char *mainHello = helloMessage.rawContent() + 5; assert((size_t)ssl->init_buf->max > mainHelloSize); memcpy(ssl->init_buf->data, mainHello, mainHelloSize); debugs(83, 5, "Hello Data init and adjustd sizes :" << ssl->init_num << " = "<< mainHelloSize); @@ -456,18 +432,18 @@ Ssl::ServerBio::write(const char *buf, int size, BIO *table) assert(helloMsg.isEmpty()); auto ssl = fd_table[fd_].ssl.get(); - if (clientFeatures.initialized_ && ssl) { + if (ssl) { if (bumpMode_ == Ssl::bumpPeek) { - if (adjustSSL(ssl, clientFeatures)) + if (adjustSSL(ssl, clientTlsDetails, clientHelloMessage)) allowBump = true; allowSplice = true; - helloMsg.append(clientFeatures.helloMessage); + helloMsg.append(clientHelloMessage); debugs(83, 7, "SSL HELLO message for FD " << fd_ << ": Random number is adjusted for peek mode"); } else { /*Ssl::bumpStare*/ allowBump = true; - if (adjustSSL(ssl, clientFeatures)) { + if (adjustSSL(ssl, clientTlsDetails, clientHelloMessage)) { allowSplice = true; - helloMsg.append(clientFeatures.helloMessage); + helloMsg.append(clientHelloMessage); debugs(83, 7, "SSL HELLO message for FD " << fd_ << ": Random number is adjusted for stare mode"); } } @@ -517,13 +493,6 @@ Ssl::ServerBio::flush(BIO *table) } } -void -Ssl::ServerBio::extractHelloFeatures() -{ - if (!receivedHelloFeatures_.initialized_) - receivedHelloFeatures_.get(rbuf, false); -} - bool Ssl::ServerBio::resumingSession() { @@ -650,472 +619,60 @@ squid_ssl_info(const SSL *ssl, int where, int ret) } } -Ssl::Bio::sslFeatures::sslFeatures(): - sslHelloVersion(-1), - sslVersion(-1), - compressMethod(-1), - helloMsgSize(0), - unknownCiphers(false), - doHeartBeats(true), - tlsTicketsExtension(false), - hasTlsTicket(false), - tlsStatusRequest(false), - initialized_(false) -{ - memset(client_random, 0, SSL3_RANDOM_SIZE); -} - -int Ssl::Bio::sslFeatures::toSquidSSLVersion() const -{ - if (sslVersion == SSL2_VERSION) - return 2; - else if (sslVersion == SSL3_VERSION) - return 3; - else if (sslVersion == TLS1_VERSION) - return 4; -#if OPENSSL_VERSION_NUMBER >= 0x10001000L - else if (sslVersion == TLS1_1_VERSION) - return 5; - else if (sslVersion == TLS1_2_VERSION) - return 6; -#endif - else - return 1; -} -bool -Ssl::Bio::sslFeatures::get(const SSL *ssl) +void +applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl::BumpMode bumpMode) { - sslVersion = SSL_version(ssl); - debugs(83, 7, "SSL version: " << SSL_get_version(ssl) << " (" << sslVersion << ")"); - + // To increase the possibility for bumping after peek mode selection or + // splicing after stare mode selection it is good to set the + // SSL protocol version. + // The SSL_set_ssl_method is not the correct method because it will strict + // SSL version which can be used to the SSL version used for client hello message. + // For example will prevent comunnicating with a tls1.0 server if the + // client sent and tlsv1.2 Hello message. #if defined(TLSEXT_NAMETYPE_host_name) - if (const char *server = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) - serverName = server; - debugs(83, 7, "SNI server name: " << serverName); -#endif - -#if !defined(OPENSSL_NO_COMP) - if (ssl->session->compress_meth) - compressMethod = ssl->session->compress_meth; - else if (sslVersion >= 3) //if it is 3 or newer version then compression is disabled -#endif - compressMethod = 0; - debugs(83, 7, "SSL compression: " << compressMethod); - - STACK_OF(SSL_CIPHER) * ciphers = NULL; - if (ssl->server) - ciphers = ssl->session->ciphers; - else - ciphers = ssl->cipher_list; - if (ciphers) { - for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) { - SSL_CIPHER *c = sk_SSL_CIPHER_value(ciphers, i); - if (c != NULL) { - if (!clientRequestedCiphers.empty()) - clientRequestedCiphers.append(":"); - clientRequestedCiphers.append(c->name); - } - } + if (!details->serverName.isEmpty()) { + SSL_set_tlsext_host_name(ssl, details->serverName.c_str()); } - debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers); - - if (sslVersion >=3 && ssl->s3 && ssl->s3->client_random[0]) { - memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE); - } - - initialized_ = true; - return true; -} - -int -Ssl::Bio::sslFeatures::parseMsgHead(const SBuf &buf) -{ - debugs(83, 7, "SSL Header: " << Raw(nullptr, buf.rawContent(), buf.length()).hex()); - - if (buf.length() < 5) - return 0; - - if (helloMsgSize > 0) - return helloMsgSize; - - const unsigned char *head = (const unsigned char *)buf.rawContent(); - // Check for SSLPlaintext/TLSPlaintext record - // RFC6101 section 5.2.1 - // RFC5246 section 6.2.1 - if (head[0] == 0x16) { - debugs(83, 7, "SSL version 3 handshake message"); - // The SSL version exist in the 2nd and 3rd bytes - sslHelloVersion = (head[1] << 8) | head[2]; - debugs(83, 7, "SSL Version :" << std::hex << std::setw(8) << std::setfill('0') << sslVersion); - // The hello message size exist in 4th and 5th bytes - helloMsgSize = (head[3] << 8) + head[4]; - debugs(83, 7, "SSL Header Size: " << helloMsgSize); - helloMsgSize +=5; - } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) { - debugs(83, 7, "SSL version 2 handshake message with v3 support"); - sslHelloVersion = 0x0002; - debugs(83, 7, "SSL Version :" << std::hex << std::setw(8) << std::setfill('0') << sslVersion); - // The hello message size exist in 2nd byte - helloMsgSize = head[1]; - helloMsgSize +=2; - } else { - debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)"); - return (helloMsgSize = -1); - } - - // Set object as initialized. Even if we did not full parsing yet - // The basic features, like the SSL version is set - initialized_ = true; - return helloMsgSize; -} - -bool -Ssl::Bio::sslFeatures::get(const SBuf &buf, bool record) -{ - int msgSize; - if ((msgSize = parseMsgHead(buf)) <= 0) { - debugs(83, 7, "Not a known SSL handshake message"); - return false; - } - - if (msgSize > (int)buf.length()) { - debugs(83, 2, "Partial SSL handshake message, can not parse!"); - return false; - } - - if (record) - helloMessage = buf; - - const unsigned char *msg = (const unsigned char *)buf.rawContent(); - if (msg[0] & 0x80) - return parseV23Hello(msg, (size_t)msgSize); - else { - // Hello messages require 5 bytes header + 1 byte Msg type + 3 bytes for Msg size - if (buf.length() < 9) - return false; - - // Check for the Handshake/Message type - // The type 2 is a ServerHello, the type 1 is a ClientHello - // RFC5246 section 7.4 - if (msg[5] == 0x2) { // ServerHello message - return parseV3ServerHello(msg, (size_t)msgSize); - } else if (msg[5] == 0x1) // ClientHello message, - return parseV3Hello(msg, (size_t)msgSize); - } - - return false; -} - -bool -Ssl::Bio::sslFeatures::parseV3ServerHello(const unsigned char *messageContainer, size_t messageContainerSize) -{ - // Parse a ServerHello Handshake message - // RFC5246 section 7.4, 7.4.1.3 - // The ServerHello starts at messageContainer + 5 - const unsigned char *serverHello = messageContainer + 5; - - // The Length field (bytes 1-3) plus 4 bytes of the serverHello message header (1 handshake type + 3 hello length) - const size_t helloSize = ((serverHello[1] << 16) | (serverHello[2] << 8) | serverHello[3]) + 4; - debugs(83, 7, "ServerHello message size: " << helloSize); - if (helloSize > messageContainerSize) { - debugs(83, 2, "ServerHello parse error"); - return false; - } - - // helloSize should be at least 38 bytes long: - // (SSL Version + Random + SessionId Length + Cipher Suite + Compression Method) - if (helloSize < 38) { - debugs(83, 2, "Too short ServerHello message"); - return false; - } - - debugs(83, 7, "Get fake features from v3 ServerHello message."); - // Get the correct version of the sub-hello message - sslVersion = (serverHello[4] << 8) | serverHello[5]; - // At the position 38 (HelloHeader (6bytes) + SSL3_RANDOM_SIZE (32bytes)) - const size_t sessIdLen = static_cast(serverHello[38]); - debugs(83, 7, "Session ID Length: " << sessIdLen); - - // The size should be enough to hold at least the following - // 4 (hello header) - // + 2 (SSL Version) + 32 (random) + 1 (sessionId length) - // + sessIdLength + 2 (cipher suite) + 1 (compression method) - // = 42 + sessIdLength - if (42 + sessIdLen > helloSize) { - debugs(83, 2, "ciphers length parse error"); - return false; - } - - // The sessionID stored at 39 position, after sessionID length field - sessionId.assign(reinterpret_cast(serverHello + 39), sessIdLen); - - // Check if there are extensions in hello message - // RFC5246 section 7.4.1.4 - if (helloSize > 42 + sessIdLen + 2) { - // 42 + sessIdLen - const unsigned char *pToExtensions = serverHello + 42 + sessIdLen; - const size_t extensionsLen = (pToExtensions[0] << 8) | pToExtensions[1]; - // Check if the hello size can hold extensions - if (42 + 2 + sessIdLen + extensionsLen > helloSize ) { - debugs(83, 2, "Extensions length parse error"); - return false; - } - - pToExtensions += 2; - const unsigned char *ext = pToExtensions; - while (ext + 4 <= pToExtensions + extensionsLen) { - const size_t extType = (ext[0] << 8) | ext[1]; - ext += 2; - const size_t extLen = (ext[0] << 8) | ext[1]; - ext += 2; - debugs(83, 7, "TLS Extension: " << std::hex << extType << " of size:" << extLen); - // SessionTicket TLS Extension, RFC5077 section 3.2 - if (extType == 0x23) { - tlsTicketsExtension = true; - } - ext += extLen; - } - } - return true; -} - -bool -Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *messageContainer, size_t messageContainerSize) -{ - // Parse a ClientHello Handshake message - // RFC5246 section 7.4, 7.4.1.2 - // The ClientHello starts at messageContainer + 5 - const unsigned char * clientHello = messageContainer + 5; - - debugs(83, 7, "Get fake features from v3 ClientHello message."); - // The Length field (bytes 1-3) plus 4 bytes of the clientHello message header (1 handshake type + 3 hello length) - const size_t helloSize = ((clientHello[1] << 16) | (clientHello[2] << 8) | clientHello[3]) + 4; - debugs(83, 7, "ClientHello message size: " << helloSize); - if (helloSize > messageContainerSize) { - debugs(83, 2, "ClientHello parse error"); - return false; - } - - // helloSize should be at least 38 bytes long: - // (SSL Version(2) + Random(32) + SessionId Length(1) + Cipher Suite Length(2) + Compression Method Length(1)) - if (helloSize < 38) { - debugs(83, 2, "Too short ClientHello message"); - return false; - } - - //For SSLv3 or TLSv1.* protocols we can get some more informations - if (messageContainer[1] != 0x3 || clientHello[0] != 0x1 /*HELLO A message*/) { - debugs(83, 2, "Not an SSLv3/TLSv1.x client hello message, stop parsing here"); - return true; - } - - // Get the correct version of the sub-hello message - sslVersion = (clientHello[4] << 8) | clientHello[5]; - //Get Client Random number. It starts on the position 6 of clientHello message - memcpy(client_random, clientHello + 6, SSL3_RANDOM_SIZE); - debugs(83, 7, "Client random: " << Raw(nullptr, (char *)client_random, SSL3_RANDOM_SIZE).hex()); - - // At the position 38 (6+SSL3_RANDOM_SIZE) - const size_t sessIDLen = static_cast(clientHello[38]); - debugs(83, 7, "Session ID Length: " << sessIDLen); - - // The helloSize should be enough to hold at least the following - // 1 handshake type + 3 hello Length - // + 2 (SSL Version) + 32 (random) + 1 (sessionId length) - // + sessIdLength + 2 (cipher suite length) + 1 (compression method length) - // = 42 + sessIdLength - if (42 + sessIDLen > helloSize) { - debugs(83, 2, "Session ID length parse error"); - return false; - } - - // The sessionID stored art 39 position, after sessionID length field - sessionId.assign(reinterpret_cast(clientHello + 39), sessIDLen); - - //Ciphers list. It is stored after the Session ID. - // It is a variable-length vector(RFC5246 section 4.3) - const unsigned char *ciphers = clientHello + 39 + sessIDLen; - const size_t ciphersLen = (ciphers[0] << 8) | ciphers[1]; - if (42 + sessIDLen + ciphersLen > helloSize) { - debugs(83, 2, "ciphers length parse error"); - return false; - } - - ciphers += 2; - if (ciphersLen) { -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - const SSL_METHOD *method = TLS_method(); -#else - const SSL_METHOD *method = SSLv23_method(); #endif - for (size_t i = 0; i < ciphersLen; i += 2) { - // each cipher in v3/tls HELLO message is of size 2 - const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i)); - if (c != NULL) { - if (!clientRequestedCiphers.empty()) - clientRequestedCiphers.append(":"); - clientRequestedCiphers.append(c->name); - } else - unknownCiphers = true; - } - } - debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers); - - // Compression field: 1 bytes the number of compression methods and - // 1 byte for each compression method - const unsigned char *compression = ciphers + ciphersLen; - if (compression[0] > 1) - compressMethod = 1; - else - compressMethod = 0; - debugs(83, 7, "SSL compression methods number: " << static_cast(compression[0])); - - // Parse Extensions, RFC5246 section 7.4.1.4 - const unsigned char *pToExtensions = compression + 1 + static_cast(compression[0]); - if ((size_t)((pToExtensions - clientHello) + 2) < helloSize) { - const size_t extensionsLen = (pToExtensions[0] << 8) | pToExtensions[1]; - if ((pToExtensions - clientHello) + 2 + extensionsLen > helloSize) { - debugs(83, 2, "Extensions length parse error"); - return false; - } - - pToExtensions += 2; - const unsigned char *ext = pToExtensions; - while (ext + 4 <= pToExtensions + extensionsLen) { - const size_t extType = (ext[0] << 8) | ext[1]; - ext += 2; - const size_t extLen = (ext[0] << 8) | ext[1]; - ext += 2; - debugs(83, 7, "TLS Extension: " << std::hex << extType << " of size:" << extLen); - - if (ext + extLen > pToExtensions + extensionsLen) { - debugs(83, 2, "Extension " << std::hex << extType << " length parser error"); - return false; - } - - //The SNI extension has the type 0 (extType == 0) - // RFC6066 sections 3, 10.2 - // The two first bytes indicates the length of the SNI data (should be extLen-2) - // The next byte is the hostname type, it should be '0' for normal hostname (ext[2] == 0) - // The 3rd and 4th bytes are the length of the hostname - if (extType == 0 && ext[2] == 0) { - const size_t hostLen = (ext[3] << 8) | ext[4]; - if (hostLen < extLen) - serverName.assign(reinterpret_cast(ext+5), hostLen); - debugs(83, 7, "Found server name: " << serverName); - } else if (extType == 15 && ext[0] != 0) { - // The heartBeats are the type 15, RFC6520 - doHeartBeats = true; - } else if (extType == 0x23) { - //SessionTicket TLS Extension RFC5077 - tlsTicketsExtension = true; - if (extLen != 0) - hasTlsTicket = true; - } else if (extType == 0x05) { - // RFC6066 sections 8, 10.2 - tlsStatusRequest = true; - } else if (extType == 0x3374) { - // detected TLS next protocol negotiate extension - } else if (extType == 0x10) { - // Application-Layer Protocol Negotiation Extension, RFC7301 - const size_t listLen = (ext[0] << 8) | ext[1]; - if (listLen < extLen) - tlsAppLayerProtoNeg.assign(reinterpret_cast(ext+5), listLen); - } else - extensions.push_back(extType); - - ext += extLen; - } - } - return true; -} -bool -Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello, size_t size) -{ - debugs(83, 7, "Get fake features from v23 ClientHello message."); - if (size < 7) - return false; - - // Get the SSL/TLS version supported by client - sslVersion = (hello[3] << 8) | hello[4]; - - //Ciphers list. It is stored after the Session ID. - const unsigned int ciphersLen = (hello[5] << 8) | hello[6]; - const unsigned char *ciphers = hello + 11; - - if (size < ciphersLen + 11) - return false; - - if (ciphersLen) { + if (!details->ciphers.empty()) { + SBuf strCiphers; + for (auto cipherId: details->ciphers) { + unsigned char cbytes[3]; + cbytes[0] = (cipherId >> 8) & 0xFF; + cbytes[1] = cipherId & 0xFF; + cbytes[2] = 0; #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - const SSL_METHOD *method = TLS_method(); + const SSL_METHOD *method = TLS_method(); #else - const SSL_METHOD *method = SSLv23_method(); + const SSL_METHOD *method = SSLv23_method(); #endif - for (unsigned int i = 0; i < ciphersLen; i += 3) { - // The v2 hello messages cipher has 3 bytes. - // The v2 cipher has the first byte not null - // Because we are going to sent only v3 message we - // are ignoring these ciphers - if (ciphers[i] != 0) - continue; - const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i + 1)); + const SSL_CIPHER *c = method->get_cipher_by_char(cbytes); if (c != NULL) { - if (!clientRequestedCiphers.empty()) - clientRequestedCiphers.append(":"); - clientRequestedCiphers.append(c->name); + if (!strCiphers.isEmpty()) + strCiphers.append(":"); + strCiphers.append(c->name); } } + if (!strCiphers.isEmpty()) + SSL_set_cipher_list(ssl, strCiphers.c_str()); } - debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers); - - const unsigned int sessionIdLength = (hello[7] << 8) | hello[8]; - debugs(83, 7, "SessionID length: " << sessionIdLength); - // SessionID starts at: hello+11+ciphersLen - if (sessionIdLength) - sessionId.assign((const char *)(hello + 11 + ciphersLen), sessionIdLength); - const unsigned int challengeLength = (hello[5] << 9) | hello[10]; - debugs(83, 7, "Challenge Length: " << challengeLength); - //challenge starts at: hello+11+ciphersLen+sessionIdLength - - compressMethod = 0; - return true; -} - -void -Ssl::Bio::sslFeatures::applyToSSL(SSL *ssl, Ssl::BumpMode bumpMode) const -{ - // To increase the possibility for bumping after peek mode selection or - // splicing after stare mode selection it is good to set the - // SSL protocol version. - // The SSL_set_ssl_method is not the correct method because it will strict - // SSL version which can be used to the SSL version used for client hello message. - // For example will prevent comunnicating with a tls1.0 server if the - // client sent and tlsv1.2 Hello message. -#if defined(TLSEXT_NAMETYPE_host_name) - if (!serverName.isEmpty()) { - SSL_set_tlsext_host_name(ssl, serverName.c_str()); - } -#endif - if (!clientRequestedCiphers.empty()) - SSL_set_cipher_list(ssl, clientRequestedCiphers.c_str()); #if defined(SSL_OP_NO_COMPRESSION) /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */ - if (compressMethod == 0) + if (details->compressMethod == 0) SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); #endif #if defined(TLSEXT_STATUSTYPE_ocsp) - if (tlsStatusRequest) + if (details->tlsStatusRequest) SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp); #endif #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) - if (!tlsAppLayerProtoNeg.isEmpty()) { + if (!details->tlsAppLayerProtoNeg.isEmpty()) { if (bumpMode == Ssl::bumpPeek) - SSL_set_alpn_protos(ssl, (const unsigned char*)tlsAppLayerProtoNeg.rawContent(), tlsAppLayerProtoNeg.length()); + SSL_set_alpn_protos(ssl, (const unsigned char*)details->tlsAppLayerProtoNeg.rawContent(), tlsAppLayerProtoNeg.length()); else { static const unsigned char supported_protos[] = {8, 'h','t','t', 'p', '/', '1', '.', '1'}; SSL_set_alpn_protos(ssl, supported_protos, sizeof(supported_protos)); @@ -1124,17 +681,4 @@ Ssl::Bio::sslFeatures::applyToSSL(SSL *ssl, Ssl::BumpMode bumpMode) const #endif } -std::ostream & -Ssl::Bio::sslFeatures::print(std::ostream &os) const -{ - static std::string buf; - // TODO: Also print missing features like the HeartBeats and AppLayerProtoNeg - return os << "v" << sslVersion << - " SNI:" << (serverName.isEmpty() ? SBuf("-") : serverName) << - " comp:" << compressMethod << - " Ciphers:" << clientRequestedCiphers << - " Random:" << Raw(nullptr, (char *)client_random, SSL3_RANDOM_SIZE).hex(); -} - -#endif /* USE_SSL */ - +#endif // USE_OPENSSL diff --git a/src/ssl/bio.h b/src/ssl/bio.h index 1399f0908d..8182f0e950 100644 --- a/src/ssl/bio.h +++ b/src/ssl/bio.h @@ -33,52 +33,6 @@ public: BIO_TO_SERVER }; - /// Class to store SSL connection features - class sslFeatures - { - public: - sslFeatures(); - bool get(const SSL *ssl); ///< Retrieves the features from SSL object - /// Retrieves features from raw SSL Hello message. - /// \param record whether to store Message to the helloMessage member - bool get(const SBuf &, bool record = true); - /// Parses a v3 ClientHello message - bool parseV3Hello(const unsigned char *hello, size_t helloSize); - /// Parses a v23 ClientHello message - bool parseV23Hello(const unsigned char *hello, size_t helloSize); - /// Parses a v3 ServerHello message. - bool parseV3ServerHello(const unsigned char *hello, size_t helloSize); - /// Prints to os stream a human readable form of sslFeatures object - std::ostream & print(std::ostream &os) const; - /// Converts to the internal squid SSL version form the sslVersion - int toSquidSSLVersion() const; - /// Configure the SSL object with the SSL features of the sslFeatures object - void applyToSSL(SSL *ssl, Ssl::BumpMode bumpMode) const; - /// Parses an SSL Message header. It returns the ssl Message size. - /// \retval >0 if the hello size is retrieved - /// \retval 0 if the contents of the buffer are not enough - /// \retval <0 if the contents of buf are not SSLv3 or TLS hello message - int parseMsgHead(const SBuf &); - public: - int sslHelloVersion; ///< The SSL hello message version - int sslVersion; ///< The requested/used SSL version - int compressMethod; ///< The requested/used compressed method - int helloMsgSize; ///< the hello message size - mutable SBuf serverName; ///< The SNI hostname, if any - std::string clientRequestedCiphers; ///< The client requested ciphers - bool unknownCiphers; ///< True if one or more ciphers are unknown - bool doHeartBeats; - bool tlsTicketsExtension; ///< whether TLS tickets extension is enabled - bool hasTlsTicket; ///< whether a TLS ticket is included - bool tlsStatusRequest; ///< whether the TLS status request extension is set - SBuf tlsAppLayerProtoNeg; ///< The value of the TLS application layer protocol extension if it is enabled - /// The client random number - unsigned char client_random[SSL3_RANDOM_SIZE]; - SBuf sessionId; - std::list extensions; - SBuf helloMessage; - bool initialized_; - }; explicit Bio(const int anFd); virtual ~Bio(); @@ -107,15 +61,16 @@ public: /// Reads data from socket and record them to a buffer int readAndBuffer(BIO *table, const char *description); - /// Return the TLS features requested by TLS client - const Bio::sslFeatures &receivedHelloFeatures() const {return receivedHelloFeatures_;} + /// Return the TLS Details requested/advirised by TLS client or server. + const Security::TlsDetails::Pointer &receivedHelloDetails() const {return parser_.details;} const SBuf &rBufData() {return rbuf;} protected: const int fd_; ///< the SSL socket we are reading and writing SBuf rbuf; ///< Used to buffer input data. /// The features retrieved from client or Server TLS hello message - Bio::sslFeatures receivedHelloFeatures_; + //Security::TlsDetails::Pointer receivedHelloDetails_; + Security::HandshakeParser parser_; ///< The SSL messages parser. }; /// BIO node to handle socket IO for squid client side @@ -124,9 +79,7 @@ protected: class ClientBio: public Bio { public: - /// The ssl hello message read states - typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived} HelloReadState; - explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), helloState(atHelloNone), helloSize(0), wrongProtocol(false) {} + explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), helloSize(0)/*, wrongProtocol(false)*/ {} /// The ClientBio version of the Ssl::Bio::stateChanged method /// When the client hello message retrieved, fill the @@ -139,19 +92,18 @@ public: /// to socket and sets the "read retry" flag of the BIO to true virtual int read(char *buf, int size, BIO *table); /// Return true if the client hello message received and analized - bool gotHello() { return (helloState == atHelloReceived); } + bool gotHello() const { return (parser_.parseDone && !parser_.parseError); } /// Prevents or allow writting on socket. void hold(bool h) {holdRead_ = holdWrite_ = h;} /// True if client does not looks like an SSL client - bool noSslClient() {return wrongProtocol;} + bool noSslClient() {return parser_.parseError;} private: /// True if the SSL state corresponds to a hello message bool isClientHello(int state); bool holdRead_; ///< The read hold state of the bio. bool holdWrite_; ///< The write hold state of the bio. - HelloReadState helloState; ///< The SSL hello read state int helloSize; ///< The SSL hello message sent by client size - bool wrongProtocol; ///< true if client SSL hello parsing failed + //bool wrongProtocol; ///< true if client SSL hello parsing failed }; /// BIO node to handle socket IO for squid server side @@ -187,11 +139,7 @@ public: /// Flushes any buffered data virtual void flush(BIO *table); /// Sets the random number to use in client SSL HELLO message - void setClientFeatures(const sslFeatures &features); - - /// Parses server Hello message if it is recorded and extracts - /// server-supported features. - void extractHelloFeatures(); + void setClientFeatures(Security::TlsDetails::Pointer const &details, SBuf const &hello); bool resumingSession(); @@ -226,7 +174,10 @@ public: const Ssl::X509_STACK_Pointer &serverCertificatesIfAny() { return parser_.serverCertificates; } /* XXX: may be nil */ private: - sslFeatures clientFeatures; ///< SSL client features extracted from ClientHello message or SSL object + /// SSL client features extracted from ClientHello message or SSL object + Security::TlsDetails::Pointer clientTlsDetails; + /// TLS client hello message, used to adapt our tls Hello message to the server + SBuf clientHelloMessage; SBuf helloMsg; ///< Used to buffer output data. mb_size_t helloMsgSize; bool helloBuild; ///< True if the client hello message sent to the server @@ -239,16 +190,12 @@ private: ///< The size of data stored in rbuf which passed to the openSSL size_t rbufConsumePos; - Security::HandshakeParser parser_; ///< The SSL messages parser. }; -inline -std::ostream &operator <<(std::ostream &os, Ssl::Bio::sslFeatures const &f) -{ - return f.print(os); -} - } // namespace Ssl +void +applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl::BumpMode bumpMode); + #endif /* SQUID_SSL_BIO_H */