From: Christos Tsantilas Date: Tue, 21 Feb 2012 17:25:53 +0000 (+0200) Subject: stable certificates part2 X-Git-Tag: BumpSslServerFirst.take05~17 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a0b971d53f489dc19ab9a4b06dd5eb34a6c3a16f;p=thirdparty%2Fsquid.git stable certificates part2 Two different certificates of the same fake Issuer must have the same serial number. Otherwise, Firefox and possibly others will display a sec_error_reused_issuer_and_serial error. Similarly, the same two certificates should have the same serial number, even if generated on different non-communicating (but identically configured) Squid boxes. To produce unique serial numbers a temporary fake certificate with serial number zero created, and its fingerprint used as the serial number of the final fake certificate. The old Ssl::CertificateDb code which was responsible to produce a serial number for generated certificates removed. --- diff --git a/src/ssl/certificate_db.cc b/src/ssl/certificate_db.cc index 381d6235ff..88e70cceb5 100644 --- a/src/ssl/certificate_db.cc +++ b/src/ssl/certificate_db.cc @@ -171,7 +171,6 @@ int Ssl::CertificateDb::index_name_cmp(const char **a, const char **b) return(strcmp(a[Ssl::CertificateDb::cnlName], b[CertificateDb::cnlName])); } -const std::string Ssl::CertificateDb::serial_file("serial"); const std::string Ssl::CertificateDb::db_file("index.txt"); const std::string Ssl::CertificateDb::cert_dir("certs"); const std::string Ssl::CertificateDb::size_file("size"); @@ -179,7 +178,6 @@ const size_t Ssl::CertificateDb::min_db_size(4096); Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_size, size_t aFs_block_size) : db_path(aDb_path), - serial_full(aDb_path + "/" + serial_file), db_full(aDb_path + "/" + db_file), cert_full(aDb_path + "/" + cert_dir), size_full(aDb_path + "/" + size_file), @@ -187,7 +185,6 @@ Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_s max_db_size(aMax_db_size), fs_block_size(aFs_block_size), dbLock(db_full), - dbSerialLock(serial_full), enabled_disk_store(true) { if (db_path.empty() && !max_db_size) @@ -219,8 +216,11 @@ bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP } row.setValue(cnlSerial, serial_string.c_str()); char ** rrow = TXT_DB_get_by_index(db.get(), cnlSerial, row.getRow()); + // We are creating certificates with unique serial number. If the serial + // number found in the database, means that the certificate already exist + // in the database if (rrow != NULL) - return false; + return true; { TidyPointer subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0)); @@ -261,56 +261,10 @@ bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP return true; } -BIGNUM * Ssl::CertificateDb::getCurrentSerialNumber() -{ - const Locker locker(dbSerialLock, Here); - // load serial number from file. - Ssl::BIO_Pointer file(BIO_new(BIO_s_file())); - if (!file) - return NULL; - - if (BIO_rw_filename(file.get(), const_cast(serial_full.c_str())) <= 0) - return NULL; - - Ssl::ASN1_INT_Pointer serial_ai(ASN1_INTEGER_new()); - if (!serial_ai) - return NULL; - - char buffer[1024]; - if (!a2i_ASN1_INTEGER(file.get(), serial_ai.get(), buffer, sizeof(buffer))) - return NULL; - - Ssl::BIGNUM_Pointer serial(ASN1_INTEGER_to_BN(serial_ai.get(), NULL)); - - if (!serial) - return NULL; - - // increase serial number. - Ssl::BIGNUM_Pointer increased_serial(BN_dup(serial.get())); - if (!increased_serial) - return NULL; - - BN_add_word(increased_serial.get(), 1); - - // save increased serial number. - if (BIO_seek(file.get(), 0)) - return NULL; - - Ssl::ASN1_INT_Pointer increased_serial_ai(BN_to_ASN1_INTEGER(increased_serial.get(), NULL)); - if (!increased_serial_ai) - return NULL; - - i2a_ASN1_INTEGER(file.get(), increased_serial_ai.get()); - BIO_puts(file.get(),"\n"); - - return serial.release(); -} - -void Ssl::CertificateDb::create(std::string const & db_path, int serial) +void Ssl::CertificateDb::create(std::string const & db_path) { if (db_path == "") throw std::runtime_error("Path to db is empty"); - std::string serial_full(db_path + "/" + serial_file); std::string db_full(db_path + "/" + db_file); std::string cert_full(db_path + "/" + cert_dir); std::string size_full(db_path + "/" + size_file); @@ -329,18 +283,6 @@ void Ssl::CertificateDb::create(std::string const & db_path, int serial) #endif throw std::runtime_error("Cannot create " + cert_full); - Ssl::ASN1_INT_Pointer i(ASN1_INTEGER_new()); - ASN1_INTEGER_set(i.get(), serial); - - Ssl::BIO_Pointer file(BIO_new(BIO_s_file())); - if (!file) - throw std::runtime_error("SSL error"); - - if (BIO_write_filename(file.get(), const_cast(serial_full.c_str())) <= 0) - throw std::runtime_error("Cannot open " + cert_full + " to open"); - - i2a_ASN1_INTEGER(file.get(), i.get()); - std::ofstream size(size_full.c_str()); if (size) size << 0; @@ -357,17 +299,6 @@ void Ssl::CertificateDb::check(std::string const & db_path, size_t max_db_size) db.load(); } -std::string Ssl::CertificateDb::getSNString() const -{ - const Locker locker(dbSerialLock, Here); - std::ifstream file(serial_full.c_str()); - if (!file) - return ""; - std::string serial; - file >> serial; - return serial; -} - bool Ssl::CertificateDb::pure_find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey) { if (!db) diff --git a/src/ssl/certificate_db.h b/src/ssl/certificate_db.h index 05841b6f26..2a31d20b7e 100644 --- a/src/ssl/certificate_db.h +++ b/src/ssl/certificate_db.h @@ -96,13 +96,10 @@ public: bool find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey); /// Save certificate to disk. bool addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName); - /// Get a serial number to use for generating a new certificate. - BIGNUM * getCurrentSerialNumber(); /// Create and initialize a database under the db_path - static void create(std::string const & db_path, int serial); + static void create(std::string const & db_path); /// Check the database stored under the db_path. static void check(std::string const & db_path, size_t max_db_size); - std::string getSNString() const; ///< Get serial number as string. bool IsEnabledDiskStore() const; ///< Check enabled of dist store. private: void load(); ///< Load db from disk. @@ -154,7 +151,6 @@ private: static IMPLEMENT_LHASH_COMP_FN(index_name_cmp,const char **) #endif - static const std::string serial_file; ///< Base name of the file to store serial number. static const std::string db_file; ///< Base name of the database index file. static const std::string cert_dir; ///< Base name of the directory to store the certs. static const std::string size_file; ///< Base name of the file to store db size. @@ -162,7 +158,6 @@ private: static const size_t min_db_size; const std::string db_path; ///< The database directory. - const std::string serial_full; ///< Full path of the file to store serial number. const std::string db_full; ///< Full path of the database index file. const std::string cert_full; ///< Full path of the directory to store the certs. const std::string size_full; ///< Full path of the file to store the db size. @@ -171,7 +166,6 @@ private: const size_t max_db_size; ///< Max size of db. const size_t fs_block_size; ///< File system block size. mutable Lock dbLock; ///< protects the database file - mutable Lock dbSerialLock; ///< protects the serial number file bool enabled_disk_store; ///< The storage on the disk is enabled. }; diff --git a/src/ssl/gadgets.cc b/src/ssl/gadgets.cc index fd7427e62b..d44fc3c3c3 100644 --- a/src/ssl/gadgets.cc +++ b/src/ssl/gadgets.cc @@ -183,7 +183,7 @@ const char *Ssl::CertAdaptAlgorithmStr[] = { NULL }; -Ssl::CertificateProperties::CertificateProperties(): serial(NULL), +Ssl::CertificateProperties::CertificateProperties(): setValidAfter(false), setValidBefore(false), setCommonName(false), @@ -289,14 +289,14 @@ static bool buildCertificate(Ssl::X509_Pointer & cert, Ssl::CertificatePropertie return true; } -bool Ssl::generateSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkeyToStore, Ssl::CertificateProperties const &properties) +static bool generateFakeSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkeyToStore, Ssl::CertificateProperties const &properties, Ssl::BIGNUM_Pointer const &serial) { Ssl::EVP_PKEY_Pointer pkey; // Use signing certificates private key as generated certificate private key if (properties.signWithPkey.get()) pkey.resetAndLock(properties.signWithPkey.get()); else // if not exist generate one - pkey.reset(createSslPrivateKey()); + pkey.reset(Ssl::createSslPrivateKey()); if (!pkey) return false; @@ -308,7 +308,7 @@ bool Ssl::generateSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_ // Set pub key and serial given by the caller if (!X509_set_pubkey(cert.get(), pkey.get())) return false; - if (!setSerialNumber(X509_get_serialNumber(cert.get()), properties.serial.get())) + if (!setSerialNumber(X509_get_serialNumber(cert.get()), serial.get())) return false; // Fill the certificate with the required properties @@ -338,6 +338,68 @@ bool Ssl::generateSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_ return true; } +/// Return the SHA1 digest of the DER encoded version of the certificate +/// stored in a BIGNUM +static BIGNUM *x509Fingerprint(Ssl::X509_Pointer const & cert) +{ + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; + + if (!X509_digest(cert.get(),EVP_sha1(),md,&n)) + return NULL; + + assert(n == 20); //for sha1 n is 20 (for md5 n is 16) + + BIGNUM *r = NULL; + r = BN_bin2bn(md, n, NULL); + return r; +} + +/// Generate a unique serial number based on a Ssl::CertificateProperties object +/// for a new generated certificate +static bool createSerial(Ssl::BIGNUM_Pointer &serial, Ssl::CertificateProperties const &properties) +{ + Ssl::EVP_PKEY_Pointer fakePkey; + Ssl::X509_Pointer fakeCert; + + serial.reset(BN_new()); + BN_zero(serial.get()); + if (!generateFakeSslCertificate(fakeCert, fakePkey, properties, serial)) + return false; + + // The x509Fingerprint return an SHA1 hash. + // both SHA1 hash and maximum serial number size are 20 bytes. + BIGNUM *r = x509Fingerprint(fakeCert); + if (!r) + return false; + + // if the serial is "0" set it to '1' + if (BN_is_zero(r)) + BN_one(r); + + // According the RFC 5280, serial is an 20 bytes ASN.1 INTEGER (a signed big integer) + // and the maximum value for X.509 certificate serial number is 2^159-1 and + // the minimum 0. If the first bit of the serial is '1' ( eg 2^160-1), + // will result to a negative integer. + // To handle this, if the produced serial is greater than 2^159-1 + // truncate the last bit + if (BN_is_bit_set(r, 159)) + BN_clear_bit(r, 159); + + serial.reset(r); + return true; +} + +bool Ssl::generateSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkeyToStore, Ssl::CertificateProperties const &properties) +{ + Ssl::BIGNUM_Pointer serial; + + if (!createSerial(serial, properties)) + return false; + + return generateFakeSslCertificate(certToStore, pkeyToStore, properties, serial); +} + /** \ingroup ServerProtocolSSLInternal * Read certificate from file. diff --git a/src/ssl/gadgets.h b/src/ssl/gadgets.h index f041796b6c..1d099ea6a8 100644 --- a/src/ssl/gadgets.h +++ b/src/ssl/gadgets.h @@ -204,7 +204,6 @@ public: X509_Pointer mimicCert; ///< Certificate to mimic X509_Pointer signWithX509; ///< Certificate to sign the generated request EVP_PKEY_Pointer signWithPkey; ///< The key of the signing certificate - BIGNUM_Pointer serial; ///< Use this serial for generated certificate bool setValidAfter; ///< Do not mimic "Not Valid After" field bool setValidBefore; ///< Do not mimic "Not Valid Before" field bool setCommonName; ///< Replace the CN field of the mimicing subject with the given diff --git a/src/ssl/ssl_crtd.cc b/src/ssl/ssl_crtd.cc index c2bec535e2..9ef7a2dba8 100644 --- a/src/ssl/ssl_crtd.cc +++ b/src/ssl/ssl_crtd.cc @@ -72,14 +72,9 @@ usage: ssl_crtd -hv -s ssl_storage_path -M storage_max_size Create new private key and certificate request for "host.dom". Sign new request by received certificate and private key. -usage: ssl_crtd -c -s ssl_store_path\n -n new_serial_number +usage: ssl_crtd -c -s ssl_store_path\n -c Init ssl db directories and exit. - -n new_serial_number HEX serial number to use when initializing db. - The default value of serial number is - the number of seconds since Epoch minus 1200000000 -usage: ssl_crtd -g -s ssl_store_path - -g Show current serial number and exit. \endverbatim */ @@ -195,13 +190,8 @@ static void usage() "-----END RSA PRIVATE KEY-----\n" "\tCreate new private key and certificate request for \"host.dom\"\n" "\tSign new request by received certificate and private key.\n" - "usage: ssl_crtd -c -s ssl_store_path -n new_serial_number\n" - "\t-c Init ssl db directories and exit.\n" - "\t-n new_serial_number HEX serial number to use when initializing db.\n" - "\t The default value of serial number is\n" - "\t the number of seconds since Epoch minus 1200000000\n" - "usage: ssl_crtd -g -s ssl_store_path\n" - "\t-g Show current serial number and exit."; + "usage: ssl_crtd -c -s ssl_store_path\n" + "\t-c Init ssl db directories and exit.\n"; std::cerr << help_string << std::endl; } @@ -233,8 +223,6 @@ static bool proccessNewRequest(Ssl::CrtdMessage & request_message, std::string c } if (!cert || !pkey) { - certProperties.serial.reset(db.getCurrentSerialNumber()); - if (!Ssl::generateSslCertificate(cert, pkey, certProperties)) throw std::runtime_error("Cannot create ssl certificate or private key."); @@ -263,12 +251,10 @@ static bool proccessNewRequest(Ssl::CrtdMessage & request_message, std::string c int main(int argc, char *argv[]) { try { - int serial = (getCurrentTime() - 1200000000); size_t max_db_size = 0; size_t fs_block_size = 2048; char c; bool create_new_db = false; - bool show_sn = false; std::string db_path; // proccess options. while ((c = getopt(argc, argv, "dcghvs:M:b:n:")) != -1) { @@ -284,11 +270,6 @@ int main(int argc, char *argv[]) case 's': db_path = optarg; break; - case 'n': { - std::stringstream sn_stream(optarg); - sn_stream >> std::hex >> serial; - break; - } case 'M': if (!parseBytesOptionValue(&max_db_size, optarg)) { throw std::runtime_error("Error when parsing -M options value"); @@ -301,9 +282,6 @@ int main(int argc, char *argv[]) case 'c': create_new_db = true; break; - case 'g': - show_sn = true; - break; case 'h': usage(); exit(0); @@ -314,16 +292,11 @@ int main(int argc, char *argv[]) if (create_new_db) { std::cout << "Initialization SSL db..." << std::endl; - Ssl::CertificateDb::create(db_path, serial); + Ssl::CertificateDb::create(db_path); std::cout << "Done" << std::endl; exit(0); } - if (show_sn) { - Ssl::CertificateDb db(db_path, 4096, 0); - std::cout << db.getSNString() << std::endl; - exit(0); - } { Ssl::CertificateDb::check(db_path, max_db_size); }