Subject | Re: [Firebird-Architect] RC4 |
---|---|
Author | Olivier Mascia |
Post date | 2010-11-15T11:17:56Z |
Le 15 nov. 2010 à 00:30, Jim Starkey a écrit :
Once per server process:
SSL_load_error_strings();
SSL_library_init();
// This will trigger RAND initialization. On most platforms the toolkit will do
// the right thing to prime the generator, using platform specific sources where they exists.
unsigned char dummy[8];
RAND_bytes(dummy, sizeof(dummy));
// On Windows, one could do this to get some rather good random seed
HCRYPTPROV hcp;
if (CryptAcquireContext(&hcp, 0, 0, PROV_RSA_FULL, CRYPT_SILENT))
{
if (CryptGenRandom(hcp, sizeof(hotBits), hotBits))
{
RAND_seed(hotBits, sizeof(hotBits));
}
CryptReleaseContext(hcp, 0);
}
Then here is some code to read a certificate from a .pem file, or to generate one and self-sign it. The example loads the key from file too, and the key isn't even encrypted in this sample. Reading an encrypted key only needs a callback to supply the password to the encrypted key. But any other way to store a cert and its key can be invented. I have left some unused or alternative code in comments.
BIO* b = BIO_new_file("server.pem", "r");
X509* ServerCert = 0;
if (b != 0)
{
ServerCert = PEM_read_bio_X509(b, 0, 0, 0);
BIO_free_all(b);
}
if (ServerCert == 0) buildNew = true;
b = BIO_new_file("server.key", "r");
RSA* ServerRSAPrivateKey = 0;
if (b != 0)
{
ServerRSAPrivateKey = PEM_read_bio_RSAPrivateKey(b, 0, 0, 0);
BIO_free_all(b);
}
if (ServerRSAPrivateKey == 0) buildNew = true;
if (! buildNew)
{
// Don't do these test if we haven't loaded both a cert and a key
SSL_CTX_use_certificate_chain_file(SSL_Context, "server.pem");
SSL_CTX_use_RSAPrivateKey(SSL_Context, ServerRSAPrivateKey);
if (! SSL_CTX_check_private_key(SSL_Context)) buildNew = true;
if (CertificateExpiresTooSoon(ServerCert)) buildNew = true;
}
if (buildNew)
{
if (ServerCert != 0) X509_free(ServerCert);
if (ServerRSAPrivateKey != 0) RSA_free(ServerRSAPrivateKey);
ServerCert = X509_new();
X509_set_version(ServerCert, 2); // X509 V3 == 2
/* Create an X509_NAME structure to hold the distinguished name */
X509_NAME* name = X509_NAME_new();
int nid;
nid = OBJ_txt2nid("CN");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)"*", -1, -1, -0);
nid = OBJ_txt2nid("O");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)"The Name", -1, -1, -0);
nid = OBJ_txt2nid("OU");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)"Firebird SQL Server", -1, -1, -0);
/*
nid = OBJ_txt2nid("ST");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)CONFIG::License::address().c_str(), -1, -1, -0);
nid = OBJ_txt2nid("L");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)CONFIG::License::city().c_str(), -1, -1, -0);
*/
/* Set subject and issuer names to the X509_NAME we made */
X509_set_issuer_name(ServerCert, name);
X509_set_subject_name(ServerCert, name);
X509_EXTENSION* extension;
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, "CA:FALSE");
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
/*
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_name_constraints, "permitted;DNS:.");
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
*/
/*
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, "keyEncipherment, dataEncipherment, keyAgreement");
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
*/
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, "serverAuth");
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
// Various ways to build a certificate valid for multiple identities. Nothing really needed for FB.
// Though might be useful.
// List all the names of this machine
// OK, this windowsish though can be adapted easily
std::set<std::string> AllNames;
addrinfo* head = 0;
getaddrinfo("", 0, 0, &head);
addrinfo* list = head;
while (list != 0)
{
str<256> host;
if (getnameinfo(list->ai_addr, list->ai_addrlen, host(), host.max_size(), 0, 0, NI_NUMERICSERV) == 0)
AllNames.insert(host.std());
if (getnameinfo(list->ai_addr, list->ai_addrlen, host(), host.max_size(), 0, 0, NI_NOFQDN | NI_NUMERICSERV) == 0)
AllNames.insert(host.std());
list = list->ai_next;
}
if (head != 0) freeaddrinfo(head);
std::string altnames("IP:127.0.0.1, DNS:localhost");
if (!integral_realm.empty()) altnames.append(", DNS:*.whatever.net");
std::set<std::string>::iterator it = AllNames.begin();
for (; it != AllNames.end(); it++)
altnames.append(", DNS:").append(*it);
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, const_cast<char*>(altnames.c_str()));
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
/* Set serial number to random - not the best idea, but using self-sign isn't too */
uint32_t serial;
RAND_bytes((unsigned char*)&serial, sizeof(serial));
serial &= 0x7FFFffff; // Reset the most significant bit
ASN1_INTEGER_set(X509_get_serialNumber(ServerCert), serial);
/* Set the valid/expiration times */
ASN1_UTCTIME* s = ASN1_UTCTIME_new();
X509_gmtime_adj(s, (-1)*60*60); // Was not valid one hour ago
X509_set_notBefore(ServerCert, s);
X509_gmtime_adj(s, (+35)*24*60*60); // Will be invalid in 35 days
X509_set_notAfter(ServerCert, s);
ASN1_UTCTIME_free(s);
// Compute a couple private/public key
ServerRSAPrivateKey = RSA_new();
BIGNUM *e = BN_new();
BN_set_bit(e, 0);
BN_set_bit(e, 16);
RSA_generate_key_ex(ServerRSAPrivateKey, 2048, e, 0);
BN_free(e);
// Assign the public key part to the certificate
EVP_PKEY* key = EVP_PKEY_new();
EVP_PKEY_assign_RSA(key, ServerRSAPrivateKey);
X509_set_pubkey(ServerCert, key);
/* Self-sign it */
X509_sign(ServerCert, key, EVP_sha1());
// Now USE that cert and key
SSL_CTX_use_certificate(SSL_Context, ServerCert);
SSL_CTX_use_RSAPrivateKey(SSL_Context, ServerRSAPrivateKey);
/*
std::cout<< "\n";
BIO* b = BIO_new_file("CON:", "a");
X509_print(b, ServerCert);
BIO_free_all(b);
*/
BIO* out = BIO_new_file("server.pem", "w");
PEM_write_bio_X509(out, ServerCert);
BIO_free_all(out);
out = BIO_new_file("server.key", "w");
PEM_write_bio_PrivateKey(out, key, NULL, NULL, 0, NULL, NULL);
BIO_free_all(out);
}
Final configuration before accepting connections would typically has some line like these:
SSL_CTX* SSL_Context = SSL_CTX_new(TLSv1_server_method());
// In this sample we deny explicitly SSLv2 and SSLv3
SSL_CTX_set_options(SSL_Context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
// And we define a preferred private set of ciphers
SSL_CTX_set_cipher_list(SSL_Context, "AES128-SHA:AES256-SHA:DES-CBC3-SHA");
Now, assume you have just returned from bsd 'accept()' system call with a new established connection and you know you want to accept only a secured connection, it would go this way:
SSL* mSSL_con = SSL_new(SSL_Context);
// Associate to socket (mSock is the socket handle)
SSL_set_fd(mSSL_con, (int)mSock);
ERR_clear_error();
SSL_set_mode(mSSL_con, SSL_MODE_AUTO_RETRY); // Might not be required for FB, I'll check that later.
if (SSL_accept(mSSL_con) != 1)
{
// You've got a problem
}
From this point, mSSL_con is the SSL'ed "socket" and you will do SSL_read and SSL_write on it instead of recv() and send(). That's where a shell class around all this is useful for the server code which doesn't have to know anything about SSL except to known when to engage it or not.
Ok, that's trash and dirty uncompilable code as is, but very close to be. Client code is similar though much shorter. It goes with a SSL_connect call after the socket connect() call, obviously.
SSL* mSSL_con = SSL_new(SSL_Context);
// Associate to socket
SSL_set_fd(mSSL_con, (int)mSock);
ERR_clear_error();
SSL_set_mode(mSSL_con, SSL_MODE_AUTO_RETRY); // Might not be required for FB, I'll check that later.
if (SSL_connect(mSSL_con) != 1)
...
Best,
—
Olivier Mascia
>> Well, this is SSL/TLS principle. Wouldn't it be preferable to re-use SSL/TLS? What rationale commands to design and implement a private scheme for exchanging the initial random session keys?Unrelated. If you think you can distinguish between a local and remote connection, so that you can enforce encryption for some connection and leave others clear, then you can do so with SSL/TLS proposal. Just enforce its use over the connections you want to and leave others free. I really do not get where a re-implemented scheme simplify anything here.
>
> A couple of reasons. One is for configurable security -- requirement
> encryption for connections outside the firewall while skipping it
> inside.
> Another is to save the administrator from the hassle ofThere is nothing to administrate. The SSL/TLS we're talking here is just a bunch algorithms and schemes used by FB code. Nothing the user herself should be put in close relation with. If FB offers some way to load an externally provided certificate, then some entry in the configuration file could be enough.
> installing, configuring, and administrating TLS and the necessary server
> certificates.
> Integrating the code into Firebird isn't at all difficultAfter SSL v2 and SSL v3, comes TLS v1, which actually is SSL v3.1 (if you look at the version tags used in the headers which were simply extended). What version to use? Really doesn't matter since the goal isn't to let web browser to connect (wide variety of clients). Just use the one which fits our needs. In this view, TLS v1 looks good.
> and means that a user has only one thing to install and nothing to
> integrate. Would TLS be mandatory?
> If not, there would need to beThis is of course how it must be done. A small light class around the bsd sockets api (which is very close to the winsock2 one on windows) which offers a single "socket" api to fb code, wether or not the current connection is free text or ssl'ed.
> either separate kits of TLS stubs to emulate the secure socket API
> against ordinary sockets.
> If it were simple, it's hard to understandJust need someone to do it at some time and it looks like it did not happen yet.
> why it hasn't already need done.
> If it isn't simple, the integrationI'd like to build a test case on the current FB code, soon. Though there are few things to actually do a TSL connection using an appropriate toolkit. If the following helps someone with good FB source code knowledge to kick start a test case, here is how it would go with OpenSSL:
> cost has to be balanced against inclusion of crypto code into mainline
> Firebird.
> If you feel strongly, why not put together an alternative architecture
> using TLS for comparison?
Once per server process:
SSL_load_error_strings();
SSL_library_init();
// This will trigger RAND initialization. On most platforms the toolkit will do
// the right thing to prime the generator, using platform specific sources where they exists.
unsigned char dummy[8];
RAND_bytes(dummy, sizeof(dummy));
// On Windows, one could do this to get some rather good random seed
HCRYPTPROV hcp;
if (CryptAcquireContext(&hcp, 0, 0, PROV_RSA_FULL, CRYPT_SILENT))
{
if (CryptGenRandom(hcp, sizeof(hotBits), hotBits))
{
RAND_seed(hotBits, sizeof(hotBits));
}
CryptReleaseContext(hcp, 0);
}
Then here is some code to read a certificate from a .pem file, or to generate one and self-sign it. The example loads the key from file too, and the key isn't even encrypted in this sample. Reading an encrypted key only needs a callback to supply the password to the encrypted key. But any other way to store a cert and its key can be invented. I have left some unused or alternative code in comments.
BIO* b = BIO_new_file("server.pem", "r");
X509* ServerCert = 0;
if (b != 0)
{
ServerCert = PEM_read_bio_X509(b, 0, 0, 0);
BIO_free_all(b);
}
if (ServerCert == 0) buildNew = true;
b = BIO_new_file("server.key", "r");
RSA* ServerRSAPrivateKey = 0;
if (b != 0)
{
ServerRSAPrivateKey = PEM_read_bio_RSAPrivateKey(b, 0, 0, 0);
BIO_free_all(b);
}
if (ServerRSAPrivateKey == 0) buildNew = true;
if (! buildNew)
{
// Don't do these test if we haven't loaded both a cert and a key
SSL_CTX_use_certificate_chain_file(SSL_Context, "server.pem");
SSL_CTX_use_RSAPrivateKey(SSL_Context, ServerRSAPrivateKey);
if (! SSL_CTX_check_private_key(SSL_Context)) buildNew = true;
if (CertificateExpiresTooSoon(ServerCert)) buildNew = true;
}
if (buildNew)
{
if (ServerCert != 0) X509_free(ServerCert);
if (ServerRSAPrivateKey != 0) RSA_free(ServerRSAPrivateKey);
ServerCert = X509_new();
X509_set_version(ServerCert, 2); // X509 V3 == 2
/* Create an X509_NAME structure to hold the distinguished name */
X509_NAME* name = X509_NAME_new();
int nid;
nid = OBJ_txt2nid("CN");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)"*", -1, -1, -0);
nid = OBJ_txt2nid("O");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)"The Name", -1, -1, -0);
nid = OBJ_txt2nid("OU");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)"Firebird SQL Server", -1, -1, -0);
/*
nid = OBJ_txt2nid("ST");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)CONFIG::License::address().c_str(), -1, -1, -0);
nid = OBJ_txt2nid("L");
X509_NAME_add_entry_by_NID(name, nid, 0x1001, (unsigned char*)CONFIG::License::city().c_str(), -1, -1, -0);
*/
/* Set subject and issuer names to the X509_NAME we made */
X509_set_issuer_name(ServerCert, name);
X509_set_subject_name(ServerCert, name);
X509_EXTENSION* extension;
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, "CA:FALSE");
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
/*
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_name_constraints, "permitted;DNS:.");
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
*/
/*
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, "keyEncipherment, dataEncipherment, keyAgreement");
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
*/
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, "serverAuth");
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
// Various ways to build a certificate valid for multiple identities. Nothing really needed for FB.
// Though might be useful.
// List all the names of this machine
// OK, this windowsish though can be adapted easily
std::set<std::string> AllNames;
addrinfo* head = 0;
getaddrinfo("", 0, 0, &head);
addrinfo* list = head;
while (list != 0)
{
str<256> host;
if (getnameinfo(list->ai_addr, list->ai_addrlen, host(), host.max_size(), 0, 0, NI_NUMERICSERV) == 0)
AllNames.insert(host.std());
if (getnameinfo(list->ai_addr, list->ai_addrlen, host(), host.max_size(), 0, 0, NI_NOFQDN | NI_NUMERICSERV) == 0)
AllNames.insert(host.std());
list = list->ai_next;
}
if (head != 0) freeaddrinfo(head);
std::string altnames("IP:127.0.0.1, DNS:localhost");
if (!integral_realm.empty()) altnames.append(", DNS:*.whatever.net");
std::set<std::string>::iterator it = AllNames.begin();
for (; it != AllNames.end(); it++)
altnames.append(", DNS:").append(*it);
extension = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, const_cast<char*>(altnames.c_str()));
X509_add_ext(ServerCert, extension, -1);
X509_EXTENSION_free(extension);
/* Set serial number to random - not the best idea, but using self-sign isn't too */
uint32_t serial;
RAND_bytes((unsigned char*)&serial, sizeof(serial));
serial &= 0x7FFFffff; // Reset the most significant bit
ASN1_INTEGER_set(X509_get_serialNumber(ServerCert), serial);
/* Set the valid/expiration times */
ASN1_UTCTIME* s = ASN1_UTCTIME_new();
X509_gmtime_adj(s, (-1)*60*60); // Was not valid one hour ago
X509_set_notBefore(ServerCert, s);
X509_gmtime_adj(s, (+35)*24*60*60); // Will be invalid in 35 days
X509_set_notAfter(ServerCert, s);
ASN1_UTCTIME_free(s);
// Compute a couple private/public key
ServerRSAPrivateKey = RSA_new();
BIGNUM *e = BN_new();
BN_set_bit(e, 0);
BN_set_bit(e, 16);
RSA_generate_key_ex(ServerRSAPrivateKey, 2048, e, 0);
BN_free(e);
// Assign the public key part to the certificate
EVP_PKEY* key = EVP_PKEY_new();
EVP_PKEY_assign_RSA(key, ServerRSAPrivateKey);
X509_set_pubkey(ServerCert, key);
/* Self-sign it */
X509_sign(ServerCert, key, EVP_sha1());
// Now USE that cert and key
SSL_CTX_use_certificate(SSL_Context, ServerCert);
SSL_CTX_use_RSAPrivateKey(SSL_Context, ServerRSAPrivateKey);
/*
std::cout<< "\n";
BIO* b = BIO_new_file("CON:", "a");
X509_print(b, ServerCert);
BIO_free_all(b);
*/
BIO* out = BIO_new_file("server.pem", "w");
PEM_write_bio_X509(out, ServerCert);
BIO_free_all(out);
out = BIO_new_file("server.key", "w");
PEM_write_bio_PrivateKey(out, key, NULL, NULL, 0, NULL, NULL);
BIO_free_all(out);
}
Final configuration before accepting connections would typically has some line like these:
SSL_CTX* SSL_Context = SSL_CTX_new(TLSv1_server_method());
// In this sample we deny explicitly SSLv2 and SSLv3
SSL_CTX_set_options(SSL_Context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
// And we define a preferred private set of ciphers
SSL_CTX_set_cipher_list(SSL_Context, "AES128-SHA:AES256-SHA:DES-CBC3-SHA");
Now, assume you have just returned from bsd 'accept()' system call with a new established connection and you know you want to accept only a secured connection, it would go this way:
SSL* mSSL_con = SSL_new(SSL_Context);
// Associate to socket (mSock is the socket handle)
SSL_set_fd(mSSL_con, (int)mSock);
ERR_clear_error();
SSL_set_mode(mSSL_con, SSL_MODE_AUTO_RETRY); // Might not be required for FB, I'll check that later.
if (SSL_accept(mSSL_con) != 1)
{
// You've got a problem
}
From this point, mSSL_con is the SSL'ed "socket" and you will do SSL_read and SSL_write on it instead of recv() and send(). That's where a shell class around all this is useful for the server code which doesn't have to know anything about SSL except to known when to engage it or not.
Ok, that's trash and dirty uncompilable code as is, but very close to be. Client code is similar though much shorter. It goes with a SSL_connect call after the socket connect() call, obviously.
SSL* mSSL_con = SSL_new(SSL_Context);
// Associate to socket
SSL_set_fd(mSSL_con, (int)mSock);
ERR_clear_error();
SSL_set_mode(mSSL_con, SSL_MODE_AUTO_RETRY); // Might not be required for FB, I'll check that later.
if (SSL_connect(mSSL_con) != 1)
...
Best,
—
Olivier Mascia