#include "build.h"
#include "copy.h"
#include "efi-fundamental.h"
+#include "env-util.h"
#include "fd-util.h"
#include "log.h"
#include "main-func.h"
return 1;
}
-static int verb_sign(int argc, char *argv[], void *userdata) {
- _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
- _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
- _cleanup_(X509_freep) X509 *certificate = NULL;
- int r;
-
- if (argc < 2)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No input file specified");
-
- if (!arg_certificate)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "No certificate specified, use --certificate=");
-
- if (!arg_private_key)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "No private key specified, use --private-key=.");
-
- if (!arg_output)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No output specified, use --output=");
-
- if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) {
- r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate);
- if (r < 0)
- return r;
- }
-
- r = openssl_load_x509_certificate(
- arg_certificate_source_type,
- arg_certificate_source,
- arg_certificate,
- &certificate);
- if (r < 0)
- return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
-
- if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
- r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key);
- if (r < 0)
- return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key);
- }
-
- r = openssl_load_private_key(
- arg_private_key_source_type,
- arg_private_key_source,
- arg_private_key,
- &(AskPasswordRequest) {
- .tty_fd = -EBADF,
- .id = "sbsign-private-key-pin",
- .keyring = arg_private_key,
- .credential = "sbsign.private-key-pin",
- .until = USEC_INFINITY,
- .hup_fd = -EBADF,
- },
- &private_key,
- &ui);
- if (r < 0)
- return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
-
- _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
- p7 = PKCS7_sign(certificate, private_key, /*certs=*/ NULL, /*data=*/ NULL, PKCS7_BINARY|PKCS7_PARTIAL);
- if (!p7)
- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate pkcs7 signing context: %s",
- ERR_error_string(ERR_get_error(), NULL));
-
- STACK_OF(PKCS7_SIGNER_INFO) *si_stack = PKCS7_get_signer_info(p7);
- if (!si_stack)
- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get pkcs7 signer info stack: %s",
- ERR_error_string(ERR_get_error(), NULL));
+static int spc_indirect_data_content_new(const void *digest, size_t digestsz, uint8_t **ret_idc, size_t *ret_idcsz) {
+ assert(digest);
+ assert(ret_idc);
+ assert(ret_idcsz);
- PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(si_stack, 0);
- if (!si)
- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get pkcs7 signer info: %s",
- ERR_error_string(ERR_get_error(), NULL));
-
- int idcnid = OBJ_create(SPC_INDIRECT_DATA_OBJID, "spcIndirectDataContext", "Indirect Data Context");
-
- if (PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(idcnid)) == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signed attribute to pkcs7 signer info: %s",
- ERR_error_string(ERR_get_error(), NULL));
-
- _cleanup_close_ int srcfd = open(argv[1], O_RDONLY|O_CLOEXEC);
- if (srcfd < 0)
- return log_error_errno(errno, "Failed to open %s: %m", argv[1]);
-
- struct stat st;
- if (fstat(srcfd, &st) < 0)
- return log_error_errno(errno, "Failed to stat %s: %m", argv[1]);
-
- r = stat_verify_regular(&st);
- if (r < 0)
- return log_error_errno(r, "%s is not a regular file: %m", argv[1]);
-
- _cleanup_(unlink_and_freep) char *tmp = NULL;
- _cleanup_close_ int dstfd = open_tmpfile_linkable(arg_output, O_RDWR|O_CLOEXEC, &tmp);
- if (dstfd < 0)
- return log_error_errno(dstfd, "Failed to open temporary file: %m");
-
- r = fchmod_umask(dstfd, 0666);
- if (r < 0)
- log_debug_errno(r, "Failed to change temporary file mode: %m");
-
- _cleanup_free_ void *hash = NULL;
- size_t hashsz;
- r = pe_hash(srcfd, EVP_sha256(), &hash, &hashsz);
- if (r < 0)
- return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]);
+ /* This function allocates and populates a new SpcIndirectDataContent object. See the authenticode
+ * spec https://aka.ms/AuthenticodeSpec for more information on the individual fields. */
/* <<<Obsolete>>> in unicode bytes. */
static const uint8_t obsolete[] = {
idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
- if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, hash, hashsz) == 0)
+ if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest: %s",
ERR_error_string(ERR_get_error(), NULL));
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcIndirectDataContent to BER: %s",
ERR_error_string(ERR_get_error(), NULL));
+ *ret_idc = TAKE_PTR(idcraw);
+ *ret_idcsz = (size_t) idcrawsz;
+
+ return 0;
+}
+
+static int asn1_timestamp(ASN1_TIME **ret) {
+ ASN1_TIME *time;
+ uint64_t epoch = UINT64_MAX;
+ int r;
+
+ assert(ret);
+
+ r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch);
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
+
+ if (epoch == UINT64_MAX) {
+ time = X509_gmtime_adj(NULL, 0);
+ if (!time)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get current time: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ } else {
+ time = ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC));
+ if (!time)
+ return log_oom();
+ }
+
+ *ret = TAKE_PTR(time);
+
+ return 0;
+}
+
+static int pkcs7_new_with_attributes(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) {
+ int r;
+
+ /* This function sets up a new PKCS#7 signing context with the signed attributes required for
+ * authenticode signing. */
+
+ assert(certificate);
+ assert(private_key);
+ assert(ret_p7);
+ assert(ret_si);
+
+ _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
+ PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
+ r = pkcs7_new(certificate, private_key, &p7, &si);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate PKCS# context: %m");
+
+ /* Add an empty SMIMECAP attribute to indicate we don't have any SMIME capabilities. */
+
+ _cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = sk_X509_ALGOR_new_null();
+ if (!smcap)
+ return log_oom();
+
+ if (PKCS7_add_attrib_smimecap(si, smcap) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add smimecap signed attribute to signer info: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ if (PKCS7_add_attrib_content_type(si, NULL) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add content type signed attribute to signer info: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL;
+ r = asn1_timestamp(&time);
+ if (r < 0)
+ return r;
+
+ if (PKCS7_add0_attrib_signing_time(si, time) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signing time signed attribute to signer info: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ TAKE_PTR(time);
+
+ ASN1_OBJECT *idc = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
+ if (!idc)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ if (PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signed attribute to pkcs7 signer info: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ *ret_p7 = TAKE_PTR(p7);
+ *ret_si = TAKE_PTR(si);
+ return 0;
+}
+
+static int pkcs7_populate_data_bio(PKCS7* p7, const void *data, size_t size, BIO **ret) {
+ assert(ret);
+
_cleanup_(BIO_free_allp) BIO *bio = PKCS7_dataInit(p7, NULL);
if (!bio)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to create PKCS7 data bio: %s",
int tag, class;
long psz;
- const uint8_t *p = idcraw;
+ const uint8_t *p = data;
/* This function weirdly enough reports errors by setting the 0x80 bit in its return value. */
- if (ASN1_get_object(&p, &psz, &tag, &class, idcrawsz) & 0x80)
+ if (ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse ASN.1 object: %s",
ERR_error_string(ERR_get_error(), NULL));
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write to PKCS7 data bio: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (PKCS7_final(p7, bio, PKCS7_BINARY) == 0)
+ *ret = TAKE_PTR(bio);
+
+ return 0;
+}
+
+static int verb_sign(int argc, char *argv[], void *userdata) {
+ _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
+ _cleanup_(X509_freep) X509 *certificate = NULL;
+ int r;
+
+ if (argc < 2)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No input file specified");
+
+ if (!arg_certificate)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "No certificate specified, use --certificate=");
+
+ if (!arg_private_key)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "No private key specified, use --private-key=.");
+
+ if (!arg_output)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No output specified, use --output=");
+
+ if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) {
+ r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate);
+ if (r < 0)
+ return r;
+ }
+
+ r = openssl_load_x509_certificate(
+ arg_certificate_source_type,
+ arg_certificate_source,
+ arg_certificate,
+ &certificate);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
+
+ if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
+ r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key);
+ }
+
+ r = openssl_load_private_key(
+ arg_private_key_source_type,
+ arg_private_key_source,
+ arg_private_key,
+ &(AskPasswordRequest) {
+ .tty_fd = -EBADF,
+ .id = "sbsign-private-key-pin",
+ .keyring = arg_private_key,
+ .credential = "sbsign.private-key-pin",
+ .until = USEC_INFINITY,
+ .hup_fd = -EBADF,
+ },
+ &private_key,
+ &ui);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
+
+ _cleanup_close_ int srcfd = open(argv[1], O_RDONLY|O_CLOEXEC);
+ if (srcfd < 0)
+ return log_error_errno(errno, "Failed to open %s: %m", argv[1]);
+
+ struct stat st;
+ if (fstat(srcfd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat %s: %m", argv[1]);
+
+ r = stat_verify_regular(&st);
+ if (r < 0)
+ return log_error_errno(r, "%s is not a regular file: %m", argv[1]);
+
+ _cleanup_(unlink_and_freep) char *tmp = NULL;
+ _cleanup_close_ int dstfd = open_tmpfile_linkable(arg_output, O_RDWR|O_CLOEXEC, &tmp);
+ if (dstfd < 0)
+ return log_error_errno(dstfd, "Failed to open temporary file: %m");
+
+ r = fchmod_umask(dstfd, 0666);
+ if (r < 0)
+ log_debug_errno(r, "Failed to change temporary file mode: %m");
+
+ _cleanup_free_ void *pehash = NULL;
+ size_t pehashsz;
+ r = pe_hash(srcfd, EVP_sha256(), &pehash, &pehashsz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]);
+
+ _cleanup_free_ uint8_t *idcraw = NULL;
+ size_t idcrawsz = 0; /* avoid false maybe-uninitialized warning */
+ r = spc_indirect_data_content_new(pehash, pehashsz, &idcraw, &idcrawsz);
+ if (r < 0)
+ return r;
+
+ _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
+ PKCS7_SIGNER_INFO *si;
+ r = pkcs7_new_with_attributes(certificate, private_key, &p7, &si);
+ if (r < 0)
+ return r;
+
+ _cleanup_(BIO_free_allp) BIO *bio = NULL;
+ r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio);
+ if (r < 0)
+ return r;
+
+ if (PKCS7_dataFinal(p7, bio) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign data: %s",
ERR_error_string(ERR_get_error(), NULL));
if (!p7c)
return log_oom();
- p7c->type = OBJ_nid2obj(idcnid);
+ p7c->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
if (!p7c->type)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s",
ERR_error_string(ERR_get_error(), NULL));