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