• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
error_msg_openssl(const char * format,...)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 */
read_private_key(const char * keyfile)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 */
read_certificate(const char * certfile)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 *
compare_fsverity_digest(const void * data,size_t size,const u8 * expected_digest,const struct fsverity_hash_alg * hash_alg)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 
sign_pkcs7(const void * data_to_sign,size_t data_size,EVP_PKEY * pkey,X509 * cert,const EVP_MD * md,void ** sig_ret,int * sig_size_ret)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 *
compare_fsverity_digest_pkcs7(const void * sig,size_t sig_len,const u8 * expected_measurement,const struct fsverity_hash_alg * hash_alg)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 
new_mem_buf(const void * buf,size_t size)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 
sign_pkcs7(const void * data_to_sign,size_t data_size,EVP_PKEY * pkey,X509 * cert,const EVP_MD * md,void ** sig_ret,int * sig_size_ret)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 *
compare_fsverity_digest_pkcs7(const void * sig,size_t sig_len,const u8 * expected_measurement,const struct fsverity_hash_alg * hash_alg)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  */
sign_data(const void * data_to_sign,size_t data_size,const char * keyfile,const char * certfile,const struct fsverity_hash_alg * hash_alg,void ** sig_ret,int * sig_size_ret)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  */
read_signature(const char * signature_file,const u8 * expected_measurement,const struct fsverity_hash_alg * hash_alg,void ** sig_ret,int * sig_size_ret)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 
write_signature(const char * signature_file,const void * sig,int sig_size)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  */
append_signed_measurement(struct filedes * out,const struct fsveritysetup_params * params,const u8 * measurement)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