diff --git a/scripts/base/protocols/ssl/main.bro b/scripts/base/protocols/ssl/main.bro index 2dcbfee5ef..c6c4091a87 100644 --- a/scripts/base/protocols/ssl/main.bro +++ b/scripts/base/protocols/ssl/main.bro @@ -67,11 +67,6 @@ export { ## (especially with large file transfers). const disable_analyzer_after_detection = T &redef; - ## The openssl command line utility. If it's in the path the default - ## value will work, otherwise a full path string can be supplied for the - ## utility. - const openssl_util = "openssl" &redef; - ## The maximum amount of time a script can delay records from being logged. const max_log_delay = 15secs &redef; diff --git a/scripts/policy/protocols/ssl/extract-certs-pem.bro b/scripts/policy/protocols/ssl/extract-certs-pem.bro index 420c60a4fd..bdd7d08586 100644 --- a/scripts/policy/protocols/ssl/extract-certs-pem.bro +++ b/scripts/policy/protocols/ssl/extract-certs-pem.bro @@ -8,10 +8,6 @@ ##! own certificate files and no duplicate checking is done across ##! clusters so each node would log each certificate. ##! -##! - If there is a certificate input based vulnerability found in the -##! openssl command line utility, you could be in trouble because this -##! script uses that utility to convert from DER to PEM certificates. -##! @load base/protocols/ssl @load base/utils/directions-and-hosts @@ -35,15 +31,32 @@ event ssl_established(c: connection) &priority=5 { if ( ! c$ssl?$cert ) return; + if ( ! addr_matches_host(c$id$resp_h, extract_certs_pem) ) return; - + if ( c$ssl$cert_hash in extracted_certs ) # If we already extracted this cert, don't do it again. return; - - add extracted_certs[c$ssl$cert_hash]; - local side = Site::is_local_addr(c$id$resp_h) ? "local" : "remote"; - local cmd = fmt("%s x509 -inform DER -outform PEM >> certs-%s.pem", openssl_util, side); - piped_exec(cmd, c$ssl$cert); + + local filename = Site::is_local_addr(c$id$resp_h) ? "certs-local.pem" : "certs-remote.pem"; + local outfile = open_for_append(filename); + + print outfile, "-----BEGIN CERTIFICATE-----"; + + # encode to base64 and format to fit 50 lines. Otherwise openssl won't like it later. + local lines = split_all(encode_base64(c$ssl$cert), /.{50}/); + local i = 1; + for ( line in lines ) + { + if ( byte_len(lines[i]) > 0 ) + { + print outfile, lines[i]; + } + i+=1; + } + + print outfile, "-----END CERTIFICATE-----"; + print outfile, ""; + close(outfile); } diff --git a/src/Base64.cc b/src/Base64.cc index b0da8ea74c..030f013d6f 100644 --- a/src/Base64.cc +++ b/src/Base64.cc @@ -1,8 +1,59 @@ #include "config.h" #include "Base64.h" +#include int Base64Decoder::default_base64_table[256]; -const string Base64Decoder::default_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +const string Base64::default_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +Base64Encoder::Base64Encoder(const string& arg_alphabet) + { + if ( arg_alphabet.size() > 0 ) + { + assert(arg_alphabet.size() == 64); + alphabet = arg_alphabet; + } + else + { + alphabet = default_alphabet; + } + } + +void Base64Encoder::Encode(int len, const unsigned char* data, int* pblen, char** pbuf) + { + int blen; + char *buf; + + if ( ! pbuf ) + reporter->InternalError("nil pointer to encoding result buffer"); + + if ( *pbuf && (*pblen % 4 != 0) ) + reporter->InternalError("Base64 encode buffer not a multiple of 4"); + + if ( *pbuf ) + { + buf = *pbuf; + blen = *pblen; + } + else + { + blen = int( 4 * ceil ( (double) len / 3 ) ); + *pbuf = buf = new char[blen]; + *pblen = blen; + } + + for ( int i = 0, j = 0; (i < len) && ( j < blen ); ) + { + uint32_t bit32 = ( ( i < len ? data[i++] : 0 ) << 16 ) + + ( ( i < len ? data[i++] : 0 & i++ ) << 8 ) + + ( i < len ? data[i++] : 0 & i++ ); + + buf[j++] = alphabet[(bit32 >> 18) & 0x3f]; + buf[j++] = alphabet[(bit32 >> 12) & 0x3f]; + buf[j++] = ( i == (len+2) ) ? '=' : alphabet[(bit32 >> 6) & 0x3f]; + buf[j++] = ( i >= (len+1) ) ? '=' : alphabet[bit32 & 0x3f]; + } + } + int* Base64Decoder::InitBase64Table(const string& alphabet) { @@ -44,6 +95,8 @@ int* Base64Decoder::InitBase64Table(const string& alphabet) return base64_table; } + + Base64Decoder::Base64Decoder(Analyzer* arg_analyzer, const string& alphabet) { base64_table = InitBase64Table(alphabet.size() ? alphabet : default_alphabet); @@ -195,3 +248,21 @@ err: delete [] rbuf; return 0; } + +BroString* encode_base64(const BroString* s, const BroString* a) + { + if ( a && a->Len() != 64 ) + { + reporter->Error("base64 alphabet is not 64 characters: %s", + a->CheckString()); + return 0; + } + + char* outbuf = 0; + int outlen = 0; + Base64Encoder enc; + enc.Encode(s->Len(), (const unsigned char*) s->Bytes(), &outlen, &outbuf); + + return new BroString(1, (u_char*)outbuf, outlen); + } + diff --git a/src/Base64.h b/src/Base64.h index 0e02c94cf0..5b94ec7c2c 100644 --- a/src/Base64.h +++ b/src/Base64.h @@ -11,7 +11,23 @@ // Maybe we should have a base class for generic decoders? -class Base64Decoder { +class Base64 { +public: +protected: + static const string default_alphabet; +}; + + +class Base64Encoder : public Base64 { +public: + Base64Encoder(const string& arg_alphabet = ""); + void Encode(int len, const unsigned char* data, int* blen, char** buf); +protected: + string alphabet; + +}; + +class Base64Decoder : public Base64 { public: // is used for error reporting, and it should be zero when // the decoder is called by the built-in function decode_base64(). @@ -51,19 +67,19 @@ protected: char error_msg[256]; protected: + static int* InitBase64Table(const string& alphabet); + static int default_base64_table[256]; char base64_group[4]; int base64_group_next; int base64_padding; int base64_after_padding; + int* base64_table; int errored; // if true, we encountered an error - skip further processing Analyzer* analyzer; - int* base64_table; - static int* InitBase64Table(const string& alphabet); - static int default_base64_table[256]; - static const string default_alphabet; }; BroString* decode_base64(const BroString* s, const BroString* a = 0); +BroString* encode_base64(const BroString* s, const BroString* a = 0); #endif /* base64_h */ diff --git a/src/bro.bif b/src/bro.bif index 8cea9d9123..3714839ab8 100644 --- a/src/bro.bif +++ b/src/bro.bif @@ -2868,6 +2868,48 @@ function bytestring_to_hexstr%(bytestring: string%): string return new StringVal(hexstr); %} +## Encodes a Base64-encoded string. +## +## s: The string to encode +## +## Returns: The encoded version of *s*. +## +## .. bro:see:: encode_base64_custom +function encode_base64%(s: string%): string + %{ + BroString* t = encode_base64(s->AsString()); + if ( t ) + return new StringVal(t); + else + { + reporter->Error("error in encoding string %s", s->CheckString()); + return new StringVal(""); + } + %} + +## Encodes a Base64-encoded string with a custom alphabet. +## +## s: The string to encode +## +## a: The custom alphabet. The empty string indicates the default alphabet. The +## length of *a* must be 64. For example, a custom alphabet could be +## ``"!#$%&/(),-.:;<>@[]^ `_{|}~abcdefghijklmnopqrstuvwxyz0123456789+?"``. +## +## Returns: The encoded version of *s*. +## +## .. bro:see:: encode_base64 +function encode_base64_custom%(s: string, a: string%): string + %{ + BroString* t = encode_base64(s->AsString(), a->AsString()); + if ( t ) + return new StringVal(t); + else + { + reporter->Error("error in encoding string %s", s->CheckString()); + return new StringVal(""); + } + %} + ## Decodes a Base64-encoded string. ## ## s: The Base64-encoded string.