#include "efi-fundamental.h"
#include "env-util.h"
#include "fd-util.h"
+#include "fileio.h"
+#include "io-util.h"
#include "log.h"
#include "main-func.h"
#include "openssl-util.h"
static char *arg_private_key = NULL;
static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
static char *arg_private_key_source = NULL;
+static bool arg_prepare_offline_signing = false;
+static char *arg_signed_data = NULL;
+static char *arg_signed_data_signature = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_output, freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_signed_data, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_signed_data_signature, freep);
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
ARG_CERTIFICATE_SOURCE,
ARG_PRIVATE_KEY,
ARG_PRIVATE_KEY_SOURCE,
+ ARG_PREPARE_OFFLINE_SIGNING,
+ ARG_SIGNED_DATA,
+ ARG_SIGNED_DATA_SIGNATURE,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "output", required_argument, NULL, ARG_OUTPUT },
- { "certificate", required_argument, NULL, ARG_CERTIFICATE },
- { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE },
- { "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
- { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "output", required_argument, NULL, ARG_OUTPUT },
+ { "certificate", required_argument, NULL, ARG_CERTIFICATE },
+ { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE },
+ { "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
+ { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE },
+ { "prepare-offline-signing", no_argument, NULL, ARG_PREPARE_OFFLINE_SIGNING },
+ { "signed-data", required_argument, NULL, ARG_SIGNED_DATA },
+ { "signed-data-signature", required_argument, NULL, ARG_SIGNED_DATA_SIGNATURE },
{}
};
break;
+ case ARG_PREPARE_OFFLINE_SIGNING:
+ arg_prepare_offline_signing = true;
+ break;
+
+ case ARG_SIGNED_DATA: {
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ case ARG_SIGNED_DATA_SIGNATURE: {
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data_signature);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
case '?':
return -EINVAL;
if (arg_private_key_source && !arg_certificate)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified.");
+ if (!!arg_signed_data != !!arg_signed_data_signature)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--signed-data= and --signed-data-signature= must always be used together.");
+
+ if (arg_prepare_offline_signing && (arg_private_key || arg_signed_data || arg_signed_data_signature))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--prepare-offline-signing cannot be used with --private-key=, --signed-data= or --signed-data-signature=");
+
return 1;
}
return 0;
}
-static int pkcs7_new_with_attributes(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) {
+static int pkcs7_new_with_attributes(
+ X509 *certificate,
+ EVP_PKEY *private_key,
+ STACK_OF(X509_ATTRIBUTE) *signed_attributes,
+ 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);
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. */
+ if (signed_attributes) {
+ si->auth_attr = signed_attributes;
+
+ *ret_p7 = TAKE_PTR(p7);
+ *ret_si = TAKE_PTR(si);
+ return 0;
+ }
+ /* 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();
return 0;
}
+static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *si) {
+ assert(p7);
+ assert(data);
+ assert(si);
+
+ BIO *mdbio = BIO_find_type(data, BIO_TYPE_MD);
+ if (!mdbio)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to find digest bio: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ EVP_MD_CTX *mdc;
+ if (BIO_get_md_ctx(mdbio, &mdc) <= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest context from bio: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ unsigned digestsz;
+
+ if (EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ if (PKCS7_add1_attrib_digest(si, digest, digestsz) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add PKCS9 message digest signed attribute to signer info: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ 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;
+ _cleanup_(x509_attribute_free_manyp) STACK_OF(X509_ATTRIBUTE) *signed_attributes = NULL;
+ _cleanup_(iovec_done) struct iovec signed_attributes_signature = {};
int r;
if (argc < 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No certificate specified, use --certificate=");
- if (!arg_private_key)
+ if (!arg_private_key && !arg_signed_data_signature && !arg_prepare_offline_signing)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "No private key specified, use --private-key=.");
+ "No private key or signed data signature specified, use --private-key= or --signed-data-signature=.");
if (!arg_output)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No output specified, use --output=");
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 (arg_private_key) {
+ 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 parse private key path %s: %m", arg_private_key);
+ return log_error_errno(r, "Failed to load private key from %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);
+ if (arg_signed_data) {
+ _cleanup_free_ void *content = NULL;
+ size_t contentsz;
+
+ r = read_full_file(arg_signed_data, (char**) &content, &contentsz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read signed attributes file '%s': %m", arg_signed_data);
+
+ const uint8_t *p = content;
+ if (!ASN1_item_d2i((ASN1_VALUE **) &signed_attributes, &p, contentsz, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN)))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse signed attributes: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+
+ if (arg_signed_data_signature) {
+ _cleanup_free_ void *content = NULL;
+ size_t contentsz;
+
+ r = read_full_file(arg_signed_data_signature, (char**) &content, &contentsz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read signed attributes signature file '%s': %m", arg_signed_data_signature);
+
+ signed_attributes_signature = IOVEC_MAKE(TAKE_PTR(content), contentsz);
+ }
_cleanup_close_ int srcfd = open(argv[1], O_RDONLY|O_CLOEXEC);
if (srcfd < 0)
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si;
- r = pkcs7_new_with_attributes(certificate, private_key, &p7, &si);
+ r = pkcs7_new_with_attributes(certificate, private_key, signed_attributes, &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;
+ TAKE_PTR(signed_attributes);
- 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 (arg_prepare_offline_signing) {
+ _cleanup_(BIO_free_allp) BIO *bio = NULL;
+ r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio);
+ if (r < 0)
+ return r;
+
+ r = pkcs7_add_digest_attribute(p7, bio, si);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ unsigned char *abuf = NULL;
+ int alen = ASN1_item_i2d((ASN1_VALUE *)si->auth_attr, &abuf, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN));
+ if (alen < 0 || !abuf)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert signed attributes ASN.1 to DER: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ r = loop_write(dstfd, abuf, alen);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write PKCS#7 DER-encoded signed attributes blob to temporary file: %m");
+
+ r = link_tmpfile(dstfd, tmp, arg_output, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC);
+ if (r < 0)
+ return log_error_errno(r, "Failed to link temporary file to %s: %m", arg_output);
+
+ log_info("Wrote PKCS#7 DER-encoded signed attributes blob to %s", arg_output);
+ return 0;
+ } else if (iovec_is_set(&signed_attributes_signature))
+ ASN1_STRING_set0(si->enc_digest, TAKE_PTR(signed_attributes_signature.iov_base), signed_attributes_signature.iov_len);
+ else {
+ _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));
+ }
_cleanup_(PKCS7_freep) PKCS7 *p7c = PKCS7_new();
if (!p7c)