diff --git a/direct/src/plugin/p3dCert.cxx b/direct/src/plugin/p3dCert.cxx index 3e73ee2e49..65689d7ec7 100644 --- a/direct/src/plugin/p3dCert.cxx +++ b/direct/src/plugin/p3dCert.cxx @@ -165,7 +165,7 @@ AuthDialog(const wxString &cert_filename, const wxString &cert_dir) : _verify_result = -1; read_cert_file(cert_filename); - get_common_name(); + get_friendly_name(); verify_cert(); layout(); } @@ -295,39 +295,52 @@ read_cert_file(const wxString &cert_filename) { } //////////////////////////////////////////////////////////////////// -// Function: AuthDialog::get_common_name +// Function: AuthDialog::get_friendly_name // Access: Private -// Description: Extracts the common_name from the certificate. +// Description: Extracts the "friendly name" from the certificate: +// the common name or email name. //////////////////////////////////////////////////////////////////// void AuthDialog:: -get_common_name() { +get_friendly_name() { if (_cert == NULL) { - _common_name.clear(); + _friendly_name.clear(); return; } - // A complex OpenSSL interface to extract out the common name in - // utf-8. - X509_NAME *xname = X509_get_subject_name(_cert); - if (xname != NULL) { - int pos = X509_NAME_get_index_by_NID(xname, NID_commonName, -1); - if (pos != -1) { - // We just get the first common name. I guess it's possible to - // have more than one; not sure what that means in this context. - X509_NAME_ENTRY *xentry = X509_NAME_get_entry(xname, pos); - if (xentry != NULL) { - ASN1_STRING *data = X509_NAME_ENTRY_get_data(xentry); - if (data != NULL) { - // We use "print" to dump the output to a memory BIO. Is - // there an easier way to decode the ASN1_STRING? Curse - // these incomplete docs. - BIO *mbio = BIO_new(BIO_s_mem()); - ASN1_STRING_print_ex(mbio, data, ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB); - char *pp; - long pp_size = BIO_get_mem_data(mbio, &pp); - _common_name = wxString(pp, wxConvUTF8, pp_size); - BIO_free(mbio); + static const int nid_choices[] = { + NID_pkcs9_emailAddress, + NID_commonName, + -1, + }; + + // Choose the first NID that exists on the cert. + for (int ni = 0; nid_choices[ni] != -1; ++ni) { + int nid = nid_choices[ni]; + + // A complex OpenSSL interface to extract out the name in utf-8. + X509_NAME *xname = X509_get_subject_name(_cert); + if (xname != NULL) { + int pos = X509_NAME_get_index_by_NID(xname, nid, -1); + if (pos != -1) { + // We just get the first common name. I guess it's possible to + // have more than one; not sure what that means in this context. + X509_NAME_ENTRY *xentry = X509_NAME_get_entry(xname, pos); + if (xentry != NULL) { + ASN1_STRING *data = X509_NAME_ENTRY_get_data(xentry); + if (data != NULL) { + // We use "print" to dump the output to a memory BIO. Is + // there an easier way to decode the ASN1_STRING? Curse + // these incomplete docs. + BIO *mbio = BIO_new(BIO_s_mem()); + ASN1_STRING_print_ex(mbio, data, ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB); + + char *pp; + long pp_size = BIO_get_mem_data(mbio, &pp); + _friendly_name = wxString(pp, wxConvUTF8, pp_size); + BIO_free(mbio); + return; + } } } } @@ -388,7 +401,7 @@ verify_cert() { X509_STORE_free(store); - cerr << "Got certificate from " << _common_name + cerr << "Got certificate from " << _friendly_name << ", verify_result = " << _verify_result << "\n"; } @@ -461,7 +474,7 @@ get_text(wxString &header, wxString &text) { break; case 0: - text.Printf(verified_cert_text, _common_name.c_str(), _common_name.c_str(), _common_name.c_str()); + text.Printf(verified_cert_text, _friendly_name.c_str(), _friendly_name.c_str(), _friendly_name.c_str()); break; case X509_V_ERR_CERT_NOT_YET_VALID: @@ -469,12 +482,12 @@ get_text(wxString &header, wxString &text) { case X509_V_ERR_CRL_NOT_YET_VALID: case X509_V_ERR_CRL_HAS_EXPIRED: header = _T("Expired signature!"); - text.Printf(expired_cert_text, _common_name.c_str()); + text.Printf(expired_cert_text, _friendly_name.c_str()); break; case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: header = _T("Unverified signature!"); - text.Printf(unknown_auth_cert_text, _common_name.c_str()); + text.Printf(unknown_auth_cert_text, _friendly_name.c_str()); break; case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: diff --git a/direct/src/plugin/p3dCert.h b/direct/src/plugin/p3dCert.h index bd32db72ac..7a32b74568 100644 --- a/direct/src/plugin/p3dCert.h +++ b/direct/src/plugin/p3dCert.h @@ -67,7 +67,7 @@ public: private: void read_cert_file(const wxString &cert_filename); - void get_common_name(); + void get_friendly_name(); void verify_cert(); void layout(); @@ -84,7 +84,7 @@ private: X509 *_cert; STACK *_stack; - wxString _common_name; + wxString _friendly_name; int _verify_result; }; diff --git a/panda/src/downloadertools/multify.cxx b/panda/src/downloadertools/multify.cxx index c397b416a6..66aabb94c3 100644 --- a/panda/src/downloadertools/multify.cxx +++ b/panda/src/downloadertools/multify.cxx @@ -224,17 +224,19 @@ help() { " generate slightly smaller files, but compression takes longer. The\n" " default is -" << default_compression_level << ".\n\n" - " -S file.crt,[chain.crt],file.key[,\"password\"]\n" + " -S file.crt[,chain.crt[,file.key[,\"password\"]]]\n" " Sign the multifile. The signing certificate should be in PEM form in\n" " file.crt, with its private key in PEM form in file.key. If the key\n" " is encrypted on-disk, specify the decryption password as the third\n" " option. If a certificate chain is required, chain.crt should also\n" - " be specified; note that the commas should be supplied even if this\n" - " optional filename is omitted.\n" + " be specified; note that the separating commas should be supplied\n" + " even if this optional filename is omitted.\n" + " You may also provide a single composite file that contains the\n" + " certificate, chain, and key in the same file.\n" " PEM form is the form accepted by the Apache web server. The\n" " signature is written to the multifile to prove it is unchanged; any\n" " subsequent change to the multifile will invalidate the signature.\n" - " This parameter may be repeated to sign the multifile with different\n" + " This parameter may be repeated to sign the multifile with additional\n" " certificates.\n\n"; } @@ -391,7 +393,13 @@ add_files(const vector_string ¶ms) { bool okflag = do_add_files(multifile, filenames); - if (multifile->needs_repack()) { + bool needs_repack = multifile->needs_repack(); + if (append) { + // If we specified -r mode, we always repack. + needs_repack = true; + } + + if (needs_repack) { if (!multifile->repack()) { cerr << "Failed to write " << multifile_name << ".\n"; okflag = false; @@ -529,27 +537,27 @@ sign_multifile() { vector_string::iterator si; for (si = sign_params.begin(); si != sign_params.end(); ++si) { const string ¶m = (*si); + Filename certificate, chain, pkey; + string password; + size_t comma1 = param.find(','); if (comma1 == string::npos) { - cerr << "Signing parameter requires two commas: " << param << "\n"; - return false; - } - size_t comma2 = param.find(',', comma1 + 1); - if (comma2 == string::npos) { - cerr << "Signing parameter requires two commas: " << param << "\n"; - return false; - } - size_t comma3 = param.find(',', comma2 + 1); - - Filename certificate = Filename::from_os_specific(param.substr(0, comma1)); - Filename chain = Filename::from_os_specific(param.substr(comma1 + 1, comma2 - comma1 - 1)); - Filename pkey; - string password; - if (comma3 != string::npos) { - pkey = Filename::from_os_specific(param.substr(comma2 + 1, comma3 - comma2 - 1)); - password = param.substr(comma3 + 1); + certificate = Filename::from_os_specific(param); } else { - pkey = Filename::from_os_specific(param.substr(comma2 + 1)); + certificate = Filename::from_os_specific(param.substr(0, comma1)); + size_t comma2 = param.find(',', comma1 + 1); + if (comma2 == string::npos) { + chain = Filename::from_os_specific(param.substr(comma1 + 1)); + } else { + chain = Filename::from_os_specific(param.substr(comma1 + 1, comma2 - comma1 - 1)); + size_t comma3 = param.find(',', comma2 + 1); + if (comma3 == string::npos) { + pkey = Filename::from_os_specific(param.substr(comma2 + 1)); + } else { + pkey = Filename::from_os_specific(param.substr(comma2 + 1, comma3 - comma2 - 1)); + password = param.substr(comma3 + 1); + } + } } if (!multifile->add_signature(certificate, chain, pkey, password)) { @@ -675,7 +683,7 @@ list_files(const vector_string ¶ms) { if (num_signatures != 0) { cout << "\n"; for (i = 0; i < num_signatures; ++i) { - cout << "Signed by " << multifile->get_signature_common_name(i); + cout << "Signed by " << multifile->get_signature_friendly_name(i); int verify_result = multifile->validate_signature_certificate(i); if (verify_result == 0) { cout << " (certificate validated)\n"; diff --git a/panda/src/express/multifile.I b/panda/src/express/multifile.I index b1bfcc0d44..7b67ead0ef 100644 --- a/panda/src/express/multifile.I +++ b/panda/src/express/multifile.I @@ -568,3 +568,14 @@ INLINE Multifile::CertRecord:: ~CertRecord() { X509_free(_cert); } + +//////////////////////////////////////////////////////////////////// +// Function: Multifile::CertRecord::Copy Assignment +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void Multifile::CertRecord:: +operator = (const Multifile::CertRecord &other) { + X509_free(_cert); + _cert = X509_dup(other._cert); +} diff --git a/panda/src/express/multifile.cxx b/panda/src/express/multifile.cxx index 7c88f1a442..34ef194bf7 100644 --- a/panda/src/express/multifile.cxx +++ b/panda/src/express/multifile.cxx @@ -598,6 +598,13 @@ add_signature(const Filename &certificate, const Filename &chain, const Filename &pkey, const string &password) { VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + if (chain.empty() && pkey.empty()) { + // If the second two filenames are empty, assume we're going for + // the composite mode, where everything's stuffed into the first + // file. + return add_signature(certificate, password); + } + CertChain cert_chain; // Read the certificate file from VFS. First, read the complete @@ -673,6 +680,98 @@ add_signature(const Filename &certificate, const Filename &chain, return result; } #endif // HAVE_OPENSSL + +#ifdef HAVE_OPENSSL +//////////////////////////////////////////////////////////////////// +// Function: Multifile::add_signature +// Access: Published +// Description: Adds a new signature to the Multifile. This +// signature associates the indicated certificate with +// the current contents of the Multifile. When the +// Multifile is read later, the signature will still be +// present only if the Multifile is unchanged; any +// subsequent changes to the Multifile will +// automatically invalidate and remove the signature. +// +// This flavor of add_signature() reads the certificate, +// private key, and certificate chain from the same +// PEM-formatted file. It takes the first private key +// found as the intended key, and then uses the first +// certificate found that matches that key as the +// signing certificate. Any other certificates in the +// file are taken to be part of the chain. +//////////////////////////////////////////////////////////////////// +bool Multifile:: +add_signature(const Filename &composite, const string &password) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + // First, read the complete file into memory. + string composite_data; + if (!vfs->read_file(composite, composite_data, true)) { + express_cat.info() + << "Could not read " << composite << ".\n"; + return false; + } + + // Get the private key. + BIO *pkey_mbio = BIO_new_mem_buf((void *)composite_data.data(), composite_data.size()); + EVP_PKEY *evp_pkey = PEM_read_bio_PrivateKey(pkey_mbio, NULL, NULL, + (void *)password.c_str()); + BIO_free(pkey_mbio); + if (evp_pkey == NULL) { + express_cat.info() + << "Could not read private key in " << composite << ".\n"; + return false; + } + + // Now read all of the certificates. + CertChain cert_chain; + + BIO *chain_mbio = BIO_new_mem_buf((void *)composite_data.data(), composite_data.size()); + X509 *c = PEM_read_bio_X509(chain_mbio, NULL, NULL, (void *)""); + while (c != NULL) { + cert_chain.push_back(c); + c = PEM_read_bio_X509(chain_mbio, NULL, NULL, (void *)""); + } + BIO_free(chain_mbio); + + if (cert_chain.empty()) { + express_cat.info() + << "Could not read certificates in " << composite << ".\n"; + return false; + } + + // Now find the certificate that matches the signature, and move it + // to the front of the chain. + size_t i; + bool found_match = false; + for (i = 0; i < cert_chain.size(); ++i) { + X509 *c = cert_chain[i]._cert; + if (X509_check_private_key(c, evp_pkey)) { + found_match = true; + if (i != 0) { + // Move this entry to the beginning. + cert_chain.insert(cert_chain.begin(), cert_chain[i]); + cert_chain.erase(cert_chain.begin() + i + 1); + } + break; + } + } + + if (!found_match) { + express_cat.info() + << "No certificates in " << composite << " match key.\n"; + return false; + } + + bool result = add_signature(cert_chain, evp_pkey); + + EVP_PKEY_free(evp_pkey); + + return result; +} +#endif // HAVE_OPENSSL + #ifdef HAVE_OPENSSL //////////////////////////////////////////////////////////////////// // Function: Multifile::add_signature @@ -756,6 +855,24 @@ add_signature(const Multifile::CertChain &cert_chain, EVP_PKEY *pkey) { return false; } } + + if (cert_chain.empty()) { + express_cat.info() + << "No certificate given.\n"; + return false; + } + + if (pkey == NULL) { + express_cat.info() + << "No private key given.\n"; + return false; + } + + if (!X509_check_private_key(cert_chain[0]._cert, pkey)) { + express_cat.info() + << "Private key does not match certificate.\n"; + return false; + } // Now encode that list of certs to a stream in DER form. stringstream der_stream; @@ -871,42 +988,54 @@ get_signature_subject_name(int n) const { #ifdef HAVE_OPENSSL //////////////////////////////////////////////////////////////////// -// Function: Multifile::get_signature_common_name +// Function: Multifile::get_signature_friendly_name // Access: Published -// Description: Returns the "common name" for the nth signature found -// on the Multifile, encoded in utf-8. This is a subset -// of the subject_name, but it is probably a friendlier -// string to present to a user. See the comments in -// get_num_signatures(). +// Description: Returns a "friendly name" for the nth signature found +// on the Multifile. This attempts to extract out the +// most meaningful part of the subject name. It returns +// the emailAddress, if it is defined; otherwise, it +// returns the commonName. + +// See the comments in get_num_signatures(). //////////////////////////////////////////////////////////////////// string Multifile:: -get_signature_common_name(int n) const { +get_signature_friendly_name(int n) const { const CertChain &cert_chain = get_signature(n); nassertr(!cert_chain.empty(), string()); - // A complex OpenSSL interface to extract out the common name in - // utf-8. - X509_NAME *xname = X509_get_subject_name(cert_chain[0]._cert); - if (xname != NULL) { - int pos = X509_NAME_get_index_by_NID(xname, NID_commonName, -1); - if (pos != -1) { - // We just get the first common name. I guess it's possible to - // have more than one; not sure what that means in this context. - X509_NAME_ENTRY *xentry = X509_NAME_get_entry(xname, pos); - if (xentry != NULL) { - ASN1_STRING *data = X509_NAME_ENTRY_get_data(xentry); - if (data != NULL) { - // We use "print" to dump the output to a memory BIO. Is - // there an easier way to decode the ASN1_STRING? Curse - // these incomplete docs. - BIO *mbio = BIO_new(BIO_s_mem()); - ASN1_STRING_print_ex(mbio, data, ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB); + static const int nid_choices[] = { + NID_pkcs9_emailAddress, + NID_commonName, + -1, + }; - char *pp; - long pp_size = BIO_get_mem_data(mbio, &pp); - string name(pp, pp_size); - BIO_free(mbio); - return name; + // Choose the first NID that exists on the cert. + for (int ni = 0; nid_choices[ni] != -1; ++ni) { + int nid = nid_choices[ni]; + + // A complex OpenSSL interface to extract out the name in utf-8. + X509_NAME *xname = X509_get_subject_name(cert_chain[0]._cert); + if (xname != NULL) { + int pos = X509_NAME_get_index_by_NID(xname, nid, -1); + if (pos != -1) { + // We just get the first common name. I guess it's possible to + // have more than one; not sure what that means in this context. + X509_NAME_ENTRY *xentry = X509_NAME_get_entry(xname, pos); + if (xentry != NULL) { + ASN1_STRING *data = X509_NAME_ENTRY_get_data(xentry); + if (data != NULL) { + // We use "print" to dump the output to a memory BIO. Is + // there an easier way to decode the ASN1_STRING? Curse + // these incomplete docs. + BIO *mbio = BIO_new(BIO_s_mem()); + ASN1_STRING_print_ex(mbio, data, ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB); + + char *pp; + long pp_size = BIO_get_mem_data(mbio, &pp); + string name(pp, pp_size); + BIO_free(mbio); + return name; + } } } } @@ -2081,6 +2210,8 @@ clear_subfiles() { delete subfile; } _cert_special.clear(); + + _signatures.clear(); #endif // HAVE_OPENSSL Subfiles::iterator fi; diff --git a/panda/src/express/multifile.h b/panda/src/express/multifile.h index aa36288f15..1d7afd0b63 100644 --- a/panda/src/express/multifile.h +++ b/panda/src/express/multifile.h @@ -89,6 +89,7 @@ PUBLISHED: INLINE CertRecord(X509 *cert); INLINE CertRecord(const CertRecord ©); INLINE ~CertRecord(); + INLINE void operator = (const CertRecord &other); X509 *_cert; }; typedef pvector CertChain; @@ -97,13 +98,15 @@ PUBLISHED: const Filename &chain, const Filename &pkey, const string &password = ""); + bool add_signature(const Filename &composite, + const string &password = ""); bool add_signature(X509 *certificate, STACK *chain, EVP_PKEY *pkey); bool add_signature(const CertChain &chain, EVP_PKEY *pkey); int get_num_signatures() const; const CertChain &get_signature(int n) const; string get_signature_subject_name(int n) const; - string get_signature_common_name(int n) const; + string get_signature_friendly_name(int n) const; string get_signature_public_key(int n) const; void write_signature_certificate(int n, ostream &out) const;