mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-30 16:58:40 -04:00
express: Add support for adding JAR-style signatures to .zip files
This is useful for writing apk/aab files in bdist_apps
This commit is contained in:
parent
c7cccd4791
commit
6a50a657be
@ -37,6 +37,27 @@ using std::string;
|
||||
// 1980-01-01 00:00:00
|
||||
static const time_t dos_epoch = 315532800;
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
/**
|
||||
* Encodes the given string using base64 encoding.
|
||||
*/
|
||||
static std::string base64_encode(const void *buf, int len) {
|
||||
BIO *b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
|
||||
BIO *sink = BIO_new(BIO_s_mem());
|
||||
BIO_push(b64, sink);
|
||||
BIO_write(b64, buf, len);
|
||||
BIO_flush(b64);
|
||||
|
||||
const char *encoded;
|
||||
const long encoded_len = BIO_get_mem_data(sink, &encoded);
|
||||
std::string result(encoded, encoded_len);
|
||||
BIO_free_all(b64);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ -412,6 +433,232 @@ update_subfile(const std::string &subfile_name, const Filename &filename,
|
||||
return name;
|
||||
}
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
/**
|
||||
* Adds a new JAR-style signature to the .zip file. The file must have been
|
||||
* opened in read/write mode.
|
||||
*
|
||||
* This implicitly causes a repack() operation if one is needed. Returns true
|
||||
* on success, false on failure.
|
||||
*
|
||||
* This flavor of add_jar_signature() reads the certificate and private key
|
||||
* from a PEM-formatted file, for instance as generated by the openssl command.
|
||||
* If the private key file is password-encrypted, the third parameter will be
|
||||
* used as the password to decrypt it.
|
||||
*
|
||||
* It's possible to add multiple signatures, by providing multiple unique
|
||||
* aliases. Note that aliases are considered case-insensitively and only the
|
||||
* first 8 characters are considered.
|
||||
*
|
||||
* There is no separate parameter to pass a certificate chain. Instead, any
|
||||
* necessary certificates are expected to be in the certificate file.
|
||||
*/
|
||||
bool ZipArchive::
|
||||
add_jar_signature(const Filename &certificate, const Filename &pkey,
|
||||
const string &password, const string &alias) {
|
||||
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
||||
|
||||
// Read the certificate file from VFS. First, read the complete file into
|
||||
// memory.
|
||||
string certificate_data;
|
||||
if (!vfs->read_file(certificate, certificate_data, true)) {
|
||||
express_cat.error()
|
||||
<< "Could not read " << certificate << ".\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now do the same thing with the private key. This one may be password-
|
||||
// encrypted on disk.
|
||||
string pkey_data;
|
||||
if (!vfs->read_file(pkey, pkey_data, true)) {
|
||||
express_cat.error()
|
||||
<< "Could not read " << pkey << ".\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create an in-memory BIO to read the "file" from the buffer.
|
||||
BIO *certificate_mbio = BIO_new_mem_buf((void *)certificate_data.data(), certificate_data.size());
|
||||
X509 *cert = PEM_read_bio_X509(certificate_mbio, nullptr, nullptr, (void *)"");
|
||||
BIO_free(certificate_mbio);
|
||||
if (cert == nullptr) {
|
||||
express_cat.error()
|
||||
<< "Could not read certificate in " << certificate << ".\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Same with private key.
|
||||
BIO *pkey_mbio = BIO_new_mem_buf((void *)pkey_data.data(), pkey_data.size());
|
||||
EVP_PKEY *evp_pkey = PEM_read_bio_PrivateKey(pkey_mbio, nullptr, nullptr,
|
||||
(void *)password.c_str());
|
||||
BIO_free(pkey_mbio);
|
||||
if (evp_pkey == nullptr) {
|
||||
express_cat.error()
|
||||
<< "Could not read private key in " << pkey << ".\n";
|
||||
|
||||
X509_free(cert);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = add_jar_signature(cert, evp_pkey, alias);
|
||||
|
||||
X509_free(cert);
|
||||
EVP_PKEY_free(evp_pkey);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif // HAVE_OPENSSL
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
/**
|
||||
* Adds a new JAR-style signature to the .zip file. The file must have been
|
||||
* opened in read/write mode.
|
||||
*
|
||||
* This implicitly causes a repack() operation if one is needed. Returns true
|
||||
* on success, false on failure.
|
||||
*
|
||||
* It's possible to add multiple signatures, by providing multiple unique
|
||||
* aliases. Note that aliases are considered case-insensitively and only the
|
||||
* first 8 characters are considered.
|
||||
*
|
||||
* The private key is expected to match the first certificate in the chain.
|
||||
*/
|
||||
bool ZipArchive::
|
||||
add_jar_signature(X509 *cert, EVP_PKEY *pkey, const std::string &alias) {
|
||||
nassertr(is_write_valid() && is_read_valid(), false);
|
||||
nassertr(cert != nullptr, false);
|
||||
nassertr(pkey != nullptr, false);
|
||||
|
||||
if (!X509_check_private_key(cert, pkey)) {
|
||||
express_cat.error()
|
||||
<< "Private key does not match certificate.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *ext;
|
||||
int algo = EVP_PKEY_base_id(pkey);
|
||||
switch (algo) {
|
||||
case EVP_PKEY_RSA:
|
||||
ext = ".RSA";
|
||||
break;
|
||||
case EVP_PKEY_DSA:
|
||||
ext = ".DSA";
|
||||
break;
|
||||
case EVP_PKEY_EC:
|
||||
ext = ".EC";
|
||||
break;
|
||||
default:
|
||||
express_cat.error()
|
||||
<< "Private key has unsupported algorithm.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanitize alias to be used in a filename.
|
||||
std::string basename;
|
||||
for (char c : alias.substr(0, 8)) {
|
||||
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_') {
|
||||
basename += c;
|
||||
}
|
||||
else if (c >= 'a' && c <= 'z') {
|
||||
basename += (c - 0x20);
|
||||
}
|
||||
else if (((uint8_t)c & 0xc0) != 0x80) {
|
||||
basename += '_';
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a MANIFEST.MF file.
|
||||
const std::string header = "Manifest-Version: 1.0\r\n\r\n";
|
||||
const std::string header_digest = "VmrRqAIgAm0FCZViZFzpaP8OfDbN4iY0MyYFuzTMPv8=";
|
||||
|
||||
std::stringstream manifest;
|
||||
SHA256_CTX manifest_ctx;
|
||||
SHA256_Init(&manifest_ctx);
|
||||
|
||||
manifest << header;
|
||||
SHA256_Update(&manifest_ctx, header.data(), header.size());
|
||||
|
||||
std::ostringstream sigfile_body;
|
||||
|
||||
for (Subfile *subfile : _subfiles) {
|
||||
nassertr(subfile != nullptr, false);
|
||||
|
||||
if (subfile->_name.compare(0, 9, "META-INF/") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string section = "Name: " + subfile->_name + "\r\n";
|
||||
sigfile_body << section;
|
||||
|
||||
// Hash the subfile.
|
||||
unsigned char digest[SHA256_DIGEST_LENGTH];
|
||||
{
|
||||
std::istream *stream = open_read_subfile(subfile);
|
||||
|
||||
SHA256_CTX subfile_ctx;
|
||||
SHA256_Init(&subfile_ctx);
|
||||
|
||||
char buffer[4096];
|
||||
stream->read(buffer, sizeof(buffer));
|
||||
size_t count = stream->gcount();
|
||||
while (count > 0) {
|
||||
SHA256_Update(&subfile_ctx, buffer, count);
|
||||
stream->read(buffer, sizeof(buffer));
|
||||
count = stream->gcount();
|
||||
}
|
||||
delete stream;
|
||||
|
||||
SHA256_Final(digest, &subfile_ctx);
|
||||
}
|
||||
|
||||
// Encode to base64.
|
||||
section += "SHA-256-Digest: " + base64_encode(digest, SHA256_DIGEST_LENGTH) + "\r\n\r\n";
|
||||
|
||||
// Encode what we just wrote to the manifest file as well.
|
||||
{
|
||||
unsigned char digest[SHA256_DIGEST_LENGTH];
|
||||
|
||||
SHA256_CTX section_ctx;
|
||||
SHA256_Init(§ion_ctx);
|
||||
SHA256_Update(§ion_ctx, section.data(), section.size());
|
||||
SHA256_Final(digest, §ion_ctx);
|
||||
|
||||
sigfile_body << "SHA-256-Digest: " << base64_encode(digest, SHA256_DIGEST_LENGTH) << "\r\n\r\n";
|
||||
}
|
||||
|
||||
manifest << section;
|
||||
SHA256_Update(&manifest_ctx, section.data(), section.size());
|
||||
}
|
||||
|
||||
// The hash for the whole manifest file goes at the beginning of the .SF file.
|
||||
std::stringstream sigfile;
|
||||
{
|
||||
unsigned char digest[SHA256_DIGEST_LENGTH];
|
||||
SHA256_Final(digest, &manifest_ctx);
|
||||
sigfile << "Signature-Version: 1.0\r\n";
|
||||
sigfile << "SHA-256-Digest-Manifest-Main-Attributes: " << header_digest << "\r\n";
|
||||
sigfile << "SHA-256-Digest-Manifest: " << base64_encode(digest, SHA256_DIGEST_LENGTH) << "\r\n\r\n";
|
||||
sigfile << sigfile_body.str();
|
||||
}
|
||||
|
||||
// Sign and convert to to DER format
|
||||
std::string sigfile_data = sigfile.str();
|
||||
BIO *sigfile_mbio = BIO_new_mem_buf((void *)sigfile_data.data(), sigfile_data.size());
|
||||
PKCS7 *p7 = PKCS7_sign(cert, pkey, nullptr, sigfile_mbio, PKCS7_DETACHED | PKCS7_NOATTR);
|
||||
int der_len = i2d_PKCS7(p7, nullptr);
|
||||
std::string signature_str(der_len, '\0');
|
||||
unsigned char *p = (unsigned char *)signature_str.data();
|
||||
i2d_PKCS7(p7, &p);
|
||||
std::istringstream signature(std::move(signature_str));
|
||||
PKCS7_free(p7);
|
||||
|
||||
add_subfile("META-INF/MANIFEST.MF", &manifest, 9);
|
||||
add_subfile("META-INF/" + basename + ".SF", &sigfile, 9);
|
||||
add_subfile("META-INF/" + basename + ext, &signature, 9);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // HAVE_OPENSSL
|
||||
|
||||
/**
|
||||
* Ensures that any changes made to the ZIP archive have been synchronized to
|
||||
* disk. In particular, this causes the central directory to be rewritten at
|
||||
|
@ -26,6 +26,11 @@
|
||||
#include "pvector.h"
|
||||
#include "vector_uchar.h"
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
typedef struct x509_st X509;
|
||||
typedef struct evp_pkey_st EVP_PKEY;
|
||||
#endif
|
||||
|
||||
// Defined by Cocoa, conflicts with the definition below.
|
||||
#undef verify
|
||||
|
||||
@ -67,6 +72,12 @@ PUBLISHED:
|
||||
std::string update_subfile(const std::string &subfile_name, const Filename &filename,
|
||||
int compression_level);
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
bool add_jar_signature(const Filename &certificate, const Filename &pkey,
|
||||
const std::string &password = "",
|
||||
const std::string &alias = "cert");
|
||||
#endif
|
||||
|
||||
BLOCKING bool flush();
|
||||
BLOCKING bool repack();
|
||||
|
||||
@ -101,6 +112,10 @@ PUBLISHED:
|
||||
INLINE const std::string &get_comment() const;
|
||||
|
||||
public:
|
||||
#ifdef HAVE_OPENSSL
|
||||
bool add_jar_signature(X509 *cert, EVP_PKEY *pkey, const std::string &alias);
|
||||
#endif // HAVE_OPENSSL
|
||||
|
||||
bool read_subfile(int index, std::string &result);
|
||||
bool read_subfile(int index, vector_uchar &result);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user