Home | History | Annotate | Download | only in fsverity-utils
      1 // SPDX-License-Identifier: GPL-2.0+
      2 /*
      3  * Signature support for 'fsverity setup'
      4  *
      5  * Copyright (C) 2018 Google LLC
      6  *
      7  * Written by Eric Biggers.
      8  */
      9 
     10 #include <fcntl.h>
     11 #include <limits.h>
     12 #include <openssl/bio.h>
     13 #include <openssl/err.h>
     14 #include <openssl/pem.h>
     15 #include <openssl/pkcs7.h>
     16 #include <stdlib.h>
     17 #include <string.h>
     18 
     19 #include "fsverity_uapi.h"
     20 #include "fsveritysetup.h"
     21 #include "hash_algs.h"
     22 
     23 static void __printf(1, 2) __cold
     24 error_msg_openssl(const char *format, ...)
     25 {
     26 	va_list va;
     27 
     28 	va_start(va, format);
     29 	do_error_msg(format, va, 0);
     30 	va_end(va);
     31 
     32 	if (ERR_peek_error() == 0)
     33 		return;
     34 
     35 	fprintf(stderr, "OpenSSL library errors:\n");
     36 	ERR_print_errors_fp(stderr);
     37 }
     38 
     39 /* Read a PEM PKCS#8 formatted private key */
     40 static EVP_PKEY *read_private_key(const char *keyfile)
     41 {
     42 	BIO *bio;
     43 	EVP_PKEY *pkey;
     44 
     45 	bio = BIO_new_file(keyfile, "r");
     46 	if (!bio) {
     47 		error_msg_openssl("can't open '%s' for reading", keyfile);
     48 		return NULL;
     49 	}
     50 
     51 	pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
     52 	if (!pkey) {
     53 		error_msg_openssl("Failed to parse private key file '%s'.\n"
     54 				  "       Note: it must be in PEM PKCS#8 format.",
     55 				  keyfile);
     56 	}
     57 	BIO_free(bio);
     58 	return pkey;
     59 }
     60 
     61 /* Read a PEM X.509 formatted certificate */
     62 static X509 *read_certificate(const char *certfile)
     63 {
     64 	BIO *bio;
     65 	X509 *cert;
     66 
     67 	bio = BIO_new_file(certfile, "r");
     68 	if (!bio) {
     69 		error_msg_openssl("can't open '%s' for reading", certfile);
     70 		return NULL;
     71 	}
     72 	cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
     73 	if (!cert) {
     74 		error_msg_openssl("Failed to parse X.509 certificate file '%s'.\n"
     75 				  "       Note: it must be in PEM format.",
     76 				  certfile);
     77 	}
     78 	BIO_free(bio);
     79 	return cert;
     80 }
     81 
     82 /*
     83  * Check that the given data is a valid 'struct fsverity_digest_disk' that
     84  * matches the given @expected_digest and @hash_alg.
     85  *
     86  * Return: NULL if the digests match, else a string describing the difference.
     87  */
     88 static const char *
     89 compare_fsverity_digest(const void *data, size_t size,
     90 			const u8 *expected_digest,
     91 			const struct fsverity_hash_alg *hash_alg)
     92 {
     93 	const struct fsverity_digest_disk *d = data;
     94 
     95 	if (size != sizeof(*d) + hash_alg->digest_size)
     96 		return "unexpected length";
     97 
     98 	if (le16_to_cpu(d->digest_algorithm) != hash_alg - fsverity_hash_algs)
     99 		return "unexpected hash algorithm";
    100 
    101 	if (le16_to_cpu(d->digest_size) != hash_alg->digest_size)
    102 		return "wrong digest size for hash algorithm";
    103 
    104 	if (memcmp(expected_digest, d->digest, hash_alg->digest_size))
    105 		return "wrong digest";
    106 
    107 	return NULL;
    108 }
    109 
    110 #ifdef OPENSSL_IS_BORINGSSL
    111 
    112 static bool sign_pkcs7(const void *data_to_sign, size_t data_size,
    113 		       EVP_PKEY *pkey, X509 *cert, const EVP_MD *md,
    114 		       void **sig_ret, int *sig_size_ret)
    115 {
    116 	CBB out, outer_seq, wrapped_seq, seq, digest_algos_set, digest_algo,
    117 		null, content_info, issuer_and_serial, signed_data,
    118 		wrapped_signed_data, signer_infos, signer_info, sign_algo,
    119 		signature;
    120 	EVP_MD_CTX md_ctx;
    121 	u8 *name_der = NULL, *sig = NULL, *pkcs7_data = NULL;
    122 	size_t pkcs7_data_len, sig_len;
    123 	int name_der_len, sig_nid;
    124 	bool ok = false;
    125 
    126 	EVP_MD_CTX_init(&md_ctx);
    127 	BIGNUM *serial = ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), NULL);
    128 
    129 	if (!CBB_init(&out, 1024)) {
    130 		error_msg("out of memory");
    131 		goto out;
    132 	}
    133 
    134 	name_der_len = i2d_X509_NAME(X509_get_subject_name(cert), &name_der);
    135 	if (name_der_len < 0) {
    136 		error_msg_openssl("i2d_X509_NAME failed");
    137 		goto out;
    138 	}
    139 
    140 	if (!EVP_DigestSignInit(&md_ctx, NULL, md, NULL, pkey)) {
    141 		error_msg_openssl("EVP_DigestSignInit failed");
    142 		goto out;
    143 	}
    144 
    145 	sig_len = EVP_PKEY_size(pkey);
    146 	sig = xmalloc(sig_len);
    147 	if (!EVP_DigestSign(&md_ctx, sig, &sig_len, data_to_sign, data_size)) {
    148 		error_msg_openssl("EVP_DigestSign failed");
    149 		goto out;
    150 	}
    151 
    152 	sig_nid = EVP_PKEY_id(pkey);
    153 	/* To mirror OpenSSL behaviour, always use |NID_rsaEncryption| with RSA
    154 	 * rather than the combined hash+pkey NID. */
    155 	if (sig_nid != NID_rsaEncryption) {
    156 		OBJ_find_sigid_by_algs(&sig_nid, EVP_MD_type(md),
    157 				       EVP_PKEY_id(pkey));
    158 	}
    159 
    160 	// See https://tools.ietf.org/html/rfc2315#section-7
    161 	if (!CBB_add_asn1(&out, &outer_seq, CBS_ASN1_SEQUENCE) ||
    162 	    !OBJ_nid2cbb(&outer_seq, NID_pkcs7_signed) ||
    163 	    !CBB_add_asn1(&outer_seq, &wrapped_seq, CBS_ASN1_CONTEXT_SPECIFIC |
    164 			  CBS_ASN1_CONSTRUCTED | 0) ||
    165 	    // See https://tools.ietf.org/html/rfc2315#section-9.1
    166 	    !CBB_add_asn1(&wrapped_seq, &seq, CBS_ASN1_SEQUENCE) ||
    167 	    !CBB_add_asn1_uint64(&seq, 1 /* version */) ||
    168 	    !CBB_add_asn1(&seq, &digest_algos_set, CBS_ASN1_SET) ||
    169 	    !CBB_add_asn1(&digest_algos_set, &digest_algo, CBS_ASN1_SEQUENCE) ||
    170 	    !OBJ_nid2cbb(&digest_algo, EVP_MD_type(md)) ||
    171 	    !CBB_add_asn1(&digest_algo, &null, CBS_ASN1_NULL) ||
    172 	    !CBB_add_asn1(&seq, &content_info, CBS_ASN1_SEQUENCE) ||
    173 	    !OBJ_nid2cbb(&content_info, NID_pkcs7_data) ||
    174 	    !CBB_add_asn1(
    175 		&content_info, &signed_data,
    176 		CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
    177 	    !CBB_add_asn1(&signed_data, &wrapped_signed_data,
    178 			  CBS_ASN1_OCTETSTRING) ||
    179 	    !CBB_add_bytes(&wrapped_signed_data, (const u8 *)data_to_sign,
    180 			   data_size) ||
    181 	    !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET) ||
    182 	    !CBB_add_asn1(&signer_infos, &signer_info, CBS_ASN1_SEQUENCE) ||
    183 	    !CBB_add_asn1_uint64(&signer_info, 1 /* version */) ||
    184 	    !CBB_add_asn1(&signer_info, &issuer_and_serial,
    185 			  CBS_ASN1_SEQUENCE) ||
    186 	    !CBB_add_bytes(&issuer_and_serial, name_der, name_der_len) ||
    187 	    !BN_marshal_asn1(&issuer_and_serial, serial) ||
    188 	    !CBB_add_asn1(&signer_info, &digest_algo, CBS_ASN1_SEQUENCE) ||
    189 	    !OBJ_nid2cbb(&digest_algo, EVP_MD_type(md)) ||
    190 	    !CBB_add_asn1(&digest_algo, &null, CBS_ASN1_NULL) ||
    191 	    !CBB_add_asn1(&signer_info, &sign_algo, CBS_ASN1_SEQUENCE) ||
    192 	    !OBJ_nid2cbb(&sign_algo, sig_nid) ||
    193 	    !CBB_add_asn1(&sign_algo, &null, CBS_ASN1_NULL) ||
    194 	    !CBB_add_asn1(&signer_info, &signature, CBS_ASN1_OCTETSTRING) ||
    195 	    !CBB_add_bytes(&signature, sig, sig_len) ||
    196 	    !CBB_finish(&out, &pkcs7_data, &pkcs7_data_len)) {
    197 		error_msg_openssl("failed to construct PKCS#7 data");
    198 		goto out;
    199 	}
    200 
    201 	*sig_ret = xmemdup(pkcs7_data, pkcs7_data_len);
    202 	*sig_size_ret = pkcs7_data_len;
    203 	ok = true;
    204 out:
    205 	BN_free(serial);
    206 	EVP_MD_CTX_cleanup(&md_ctx);
    207 	CBB_cleanup(&out);
    208 	free(sig);
    209 	OPENSSL_free(name_der);
    210 	OPENSSL_free(pkcs7_data);
    211 	return ok;
    212 }
    213 
    214 static const char *
    215 compare_fsverity_digest_pkcs7(const void *sig, size_t sig_len,
    216 			      const u8 *expected_measurement,
    217 			      const struct fsverity_hash_alg *hash_alg)
    218 {
    219 	CBS in, content_info, content_type, wrapped_signed_data, signed_data,
    220 		content, wrapped_data, data;
    221 	u64 version;
    222 
    223 	CBS_init(&in, sig, sig_len);
    224 	if (!CBS_get_asn1(&in, &content_info, CBS_ASN1_SEQUENCE) ||
    225 	    !CBS_get_asn1(&content_info, &content_type, CBS_ASN1_OBJECT) ||
    226 	    (OBJ_cbs2nid(&content_type) != NID_pkcs7_signed) ||
    227 	    !CBS_get_asn1(
    228 		&content_info, &wrapped_signed_data,
    229 		CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
    230 	    !CBS_get_asn1(&wrapped_signed_data, &signed_data,
    231 			  CBS_ASN1_SEQUENCE) ||
    232 	    !CBS_get_asn1_uint64(&signed_data, &version) ||
    233 	    (version < 1) ||
    234 	    !CBS_get_asn1(&signed_data, NULL /* digests */, CBS_ASN1_SET) ||
    235 	    !CBS_get_asn1(&signed_data, &content, CBS_ASN1_SEQUENCE) ||
    236 	    !CBS_get_asn1(&content, &content_type, CBS_ASN1_OBJECT) ||
    237 	    (OBJ_cbs2nid(&content_type) != NID_pkcs7_data) ||
    238 	    !CBS_get_asn1(&content, &wrapped_data, CBS_ASN1_CONTEXT_SPECIFIC |
    239 						   CBS_ASN1_CONSTRUCTED | 0) ||
    240 	    !CBS_get_asn1(&wrapped_data, &data, CBS_ASN1_OCTETSTRING)) {
    241 		return "invalid PKCS#7 data";
    242 	}
    243 
    244 	return compare_fsverity_digest(CBS_data(&data), CBS_len(&data),
    245 				       expected_measurement, hash_alg);
    246 }
    247 
    248 #else /* OPENSSL_IS_BORINGSSL */
    249 
    250 static BIO *new_mem_buf(const void *buf, size_t size)
    251 {
    252 	BIO *bio;
    253 
    254 	ASSERT(size <= INT_MAX);
    255 	/*
    256 	 * Prior to OpenSSL 1.1.0, BIO_new_mem_buf() took a non-const pointer,
    257 	 * despite still marking the resulting bio as read-only.  So cast away
    258 	 * the const to avoid a compiler warning with older OpenSSL versions.
    259 	 */
    260 	bio = BIO_new_mem_buf((void *)buf, size);
    261 	if (!bio)
    262 		error_msg_openssl("out of memory");
    263 	return bio;
    264 }
    265 
    266 static bool sign_pkcs7(const void *data_to_sign, size_t data_size,
    267 		       EVP_PKEY *pkey, X509 *cert, const EVP_MD *md,
    268 		       void **sig_ret, int *sig_size_ret)
    269 {
    270 	/*
    271 	 * PKCS#7 signing flags:
    272 	 *
    273 	 * - PKCS7_BINARY	signing binary data, so skip MIME translation
    274 	 *
    275 	 * - PKCS7_NOATTR	omit extra authenticated attributes, such as
    276 	 *			SMIMECapabilities
    277 	 *
    278 	 * - PKCS7_NOCERTS	omit the signer's certificate
    279 	 *
    280 	 * - PKCS7_PARTIAL	PKCS7_sign() creates a handle only, then
    281 	 *			PKCS7_sign_add_signer() can add a signer later.
    282 	 *			This is necessary to change the message digest
    283 	 *			algorithm from the default of SHA-1.  Requires
    284 	 *			OpenSSL 1.0.0 or later.
    285 	 */
    286 	int pkcs7_flags = PKCS7_BINARY | PKCS7_NOATTR | PKCS7_NOCERTS |
    287 			  PKCS7_PARTIAL;
    288 	void *sig;
    289 	int sig_size;
    290 	BIO *bio = NULL;
    291 	PKCS7 *p7 = NULL;
    292 	bool ok = false;
    293 
    294 	bio = new_mem_buf(data_to_sign, data_size);
    295 	if (!bio)
    296 		goto out;
    297 
    298 	p7 = PKCS7_sign(NULL, NULL, NULL, bio, pkcs7_flags);
    299 	if (!p7) {
    300 		error_msg_openssl("failed to initialize PKCS#7 signature object");
    301 		goto out;
    302 	}
    303 
    304 	if (!PKCS7_sign_add_signer(p7, cert, pkey, md, pkcs7_flags)) {
    305 		error_msg_openssl("failed to add signer to PKCS#7 signature object");
    306 		goto out;
    307 	}
    308 
    309 	if (PKCS7_final(p7, bio, pkcs7_flags) != 1) {
    310 		error_msg_openssl("failed to finalize PKCS#7 signature");
    311 		goto out;
    312 	}
    313 
    314 	BIO_free(bio);
    315 	bio = BIO_new(BIO_s_mem());
    316 	if (!bio) {
    317 		error_msg_openssl("out of memory");
    318 		goto out;
    319 	}
    320 
    321 	if (i2d_PKCS7_bio(bio, p7) != 1) {
    322 		error_msg_openssl("failed to DER-encode PKCS#7 signature object");
    323 		goto out;
    324 	}
    325 
    326 	sig_size = BIO_get_mem_data(bio, &sig);
    327 	*sig_ret = xmemdup(sig, sig_size);
    328 	*sig_size_ret = sig_size;
    329 	ok = true;
    330 out:
    331 	PKCS7_free(p7);
    332 	BIO_free(bio);
    333 	return ok;
    334 }
    335 
    336 static const char *
    337 compare_fsverity_digest_pkcs7(const void *sig, size_t sig_len,
    338 			      const u8 *expected_measurement,
    339 			      const struct fsverity_hash_alg *hash_alg)
    340 {
    341 	BIO *bio = NULL;
    342 	PKCS7 *p7 = NULL;
    343 	const char *reason = NULL;
    344 
    345 	bio = new_mem_buf(sig, sig_len);
    346 	if (!bio)
    347 		return "out of memory";
    348 
    349 	p7 = d2i_PKCS7_bio(bio, NULL);
    350 	if (!p7) {
    351 		reason = "failed to decode PKCS#7 signature";
    352 		goto out;
    353 	}
    354 
    355 	if (OBJ_obj2nid(p7->type) != NID_pkcs7_signed ||
    356 	    OBJ_obj2nid(p7->d.sign->contents->type) != NID_pkcs7_data) {
    357 		reason = "unexpected PKCS#7 content type";
    358 	} else {
    359 		const ASN1_OCTET_STRING *o = p7->d.sign->contents->d.data;
    360 
    361 		reason = compare_fsverity_digest(o->data, o->length,
    362 						 expected_measurement,
    363 						 hash_alg);
    364 	}
    365 out:
    366 	BIO_free(bio);
    367 	PKCS7_free(p7);
    368 	return reason;
    369 }
    370 
    371 #endif /* !OPENSSL_IS_BORINGSSL */
    372 
    373 /*
    374  * Sign the specified @data_to_sign of length @data_size bytes using the private
    375  * key in @keyfile, the certificate in @certfile, and the hash algorithm
    376  * @hash_alg.  Returns the DER-formatted PKCS#7 signature, with the signed data
    377  * included (not detached), in @sig_ret and @sig_size_ret.
    378  */
    379 static bool sign_data(const void *data_to_sign, size_t data_size,
    380 		      const char *keyfile, const char *certfile,
    381 		      const struct fsverity_hash_alg *hash_alg,
    382 		      void **sig_ret, int *sig_size_ret)
    383 {
    384 	EVP_PKEY *pkey = NULL;
    385 	X509 *cert = NULL;
    386 	const EVP_MD *md;
    387 	bool ok = false;
    388 
    389 	pkey = read_private_key(keyfile);
    390 	if (!pkey)
    391 		goto out;
    392 
    393 	cert = read_certificate(certfile);
    394 	if (!cert)
    395 		goto out;
    396 
    397 	OpenSSL_add_all_digests();
    398 	ASSERT(hash_alg->cryptographic);
    399 	md = EVP_get_digestbyname(hash_alg->name);
    400 	if (!md) {
    401 		fprintf(stderr,
    402 			"Warning: '%s' algorithm not found in OpenSSL library.\n"
    403 			"         Falling back to SHA-256 signature.\n",
    404 			hash_alg->name);
    405 		md = EVP_sha256();
    406 	}
    407 
    408 	ok = sign_pkcs7(data_to_sign, data_size, pkey, cert, md,
    409 			sig_ret, sig_size_ret);
    410 out:
    411 	EVP_PKEY_free(pkey);
    412 	X509_free(cert);
    413 	return ok;
    414 }
    415 
    416 /*
    417  * Read a file measurement signature in PKCS#7 DER format from @signature_file,
    418  * validate that the signed data matches the expected measurement, then return
    419  * the PKCS#7 DER message in @sig_ret and @sig_size_ret.
    420  */
    421 static bool read_signature(const char *signature_file,
    422 			   const u8 *expected_measurement,
    423 			   const struct fsverity_hash_alg *hash_alg,
    424 			   void **sig_ret, int *sig_size_ret)
    425 {
    426 	struct filedes file = { .fd = -1 };
    427 	u64 filesize;
    428 	void *sig = NULL;
    429 	bool ok = false;
    430 	const char *reason;
    431 
    432 	if (!open_file(&file, signature_file, O_RDONLY, 0))
    433 		goto out;
    434 	if (!get_file_size(&file, &filesize))
    435 		goto out;
    436 	if (filesize <= 0) {
    437 		error_msg("signature file '%s' is empty", signature_file);
    438 		goto out;
    439 	}
    440 	if (filesize > 1000000) {
    441 		error_msg("signature file '%s' is too large", signature_file);
    442 		goto out;
    443 	}
    444 	sig = xmalloc(filesize);
    445 	if (!full_read(&file, sig, filesize))
    446 		goto out;
    447 
    448 	reason = compare_fsverity_digest_pkcs7(sig, filesize,
    449 					       expected_measurement, hash_alg);
    450 	if (reason) {
    451 		error_msg("signed file measurement from '%s' is invalid (%s)",
    452 			  signature_file, reason);
    453 		goto out;
    454 	}
    455 
    456 	printf("Using existing signed file measurement from '%s'\n",
    457 	       signature_file);
    458 	*sig_ret = sig;
    459 	*sig_size_ret = filesize;
    460 	sig = NULL;
    461 	ok = true;
    462 out:
    463 	filedes_close(&file);
    464 	free(sig);
    465 	return ok;
    466 }
    467 
    468 static bool write_signature(const char *signature_file,
    469 			    const void *sig, int sig_size)
    470 {
    471 	struct filedes file;
    472 	bool ok;
    473 
    474 	if (!open_file(&file, signature_file, O_WRONLY|O_CREAT|O_TRUNC, 0644))
    475 		return false;
    476 	ok = full_write(&file, sig, sig_size);
    477 	ok &= filedes_close(&file);
    478 	if (ok)
    479 		printf("Wrote signed file measurement to '%s'\n",
    480 		       signature_file);
    481 	return ok;
    482 }
    483 
    484 /*
    485  * Append the signed file measurement to the output file as a PKCS7_SIGNATURE
    486  * extension item.
    487  *
    488  * Return: exit status code (0 on success, nonzero on failure)
    489  */
    490 int append_signed_measurement(struct filedes *out,
    491 			      const struct fsveritysetup_params *params,
    492 			      const u8 *measurement)
    493 {
    494 	struct fsverity_digest_disk *data_to_sign = NULL;
    495 	void *sig = NULL;
    496 	void *extbuf = NULL;
    497 	void *tmp;
    498 	int sig_size;
    499 	int status;
    500 
    501 	if (params->signing_key_file) {
    502 		size_t data_size = sizeof(*data_to_sign) +
    503 				   params->hash_alg->digest_size;
    504 
    505 		/* Sign the file measurement using the given key */
    506 
    507 		data_to_sign = xzalloc(data_size);
    508 		data_to_sign->digest_algorithm =
    509 			cpu_to_le16(params->hash_alg - fsverity_hash_algs);
    510 		data_to_sign->digest_size =
    511 			cpu_to_le16(params->hash_alg->digest_size);
    512 		memcpy(data_to_sign->digest, measurement,
    513 		       params->hash_alg->digest_size);
    514 
    515 		ASSERT(compare_fsverity_digest(data_to_sign, data_size,
    516 					measurement, params->hash_alg) == NULL);
    517 
    518 		if (!sign_data(data_to_sign, data_size,
    519 			       params->signing_key_file,
    520 			       params->signing_cert_file ?:
    521 			       params->signing_key_file,
    522 			       params->hash_alg,
    523 			       &sig, &sig_size))
    524 			goto out_err;
    525 
    526 		if (params->signature_file &&
    527 		    !write_signature(params->signature_file, sig, sig_size))
    528 			goto out_err;
    529 	} else {
    530 		/* Using a signature that was already created */
    531 		if (!read_signature(params->signature_file, measurement,
    532 				    params->hash_alg, &sig, &sig_size))
    533 			goto out_err;
    534 	}
    535 
    536 	tmp = extbuf = xzalloc(FSVERITY_EXTLEN(sig_size));
    537 	fsverity_append_extension(&tmp, FS_VERITY_EXT_PKCS7_SIGNATURE,
    538 				  sig, sig_size);
    539 	ASSERT(tmp == extbuf + FSVERITY_EXTLEN(sig_size));
    540 	if (!full_write(out, extbuf, FSVERITY_EXTLEN(sig_size)))
    541 		goto out_err;
    542 	status = 0;
    543 out:
    544 	free(data_to_sign);
    545 	free(sig);
    546 	free(extbuf);
    547 	return status;
    548 
    549 out_err:
    550 	status = 1;
    551 	goto out;
    552 }
    553