| // Copyright 2015 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "attestation/server/attestation_service.h" |
| |
| #include <algorithm> |
| #include <climits> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <attestation/proto_bindings/attestation_ca.pb.h> |
| #include <base/bind.h> |
| #include <base/bind_helpers.h> |
| #include <base/callback.h> |
| #include <base/hash/sha1.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/cryptohome.h> |
| #include <brillo/data_encoding.h> |
| #include <crypto/sha2.h> |
| #include <openssl/objects.h> |
| #include <policy/device_policy.h> |
| #include <policy/libpolicy.h> |
| #if USE_TPM2 |
| #include <trunks/tpm_utility.h> |
| #endif |
| extern "C" { |
| #if USE_TPM2 |
| #include <trunks/cr50_headers/virtual_nvmem.h> |
| #endif |
| #include <vboot/crossystem.h> |
| } |
| |
| #include "attestation/common/database.pb.h" |
| #include "attestation/common/tpm_utility_factory.h" |
| #include "attestation/pca_agent/client/proxy_factory.h" |
| #include "attestation/server/attestation_flow.h" |
| #include "attestation/server/database_impl.h" |
| #include "attestation/server/google_keys.h" |
| |
| namespace { |
| |
| const size_t kNonceSize = 20; // As per TPM_NONCE definition. |
| const int kNumTemporalValues = 5; |
| |
| const char kKnownBootModes[8][3] = {{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}, |
| {1, 0, 0}, {1, 0, 1}, {1, 1, 0}, {1, 1, 1}}; |
| const char kVerifiedBootMode[3] = {0, 0, 1}; |
| |
| // Context name to derive stable secret for attestation-based enterprise |
| // enrollment. |
| const char kAttestationBasedEnterpriseEnrollmentContextName[] = |
| "attestation_based_enrollment"; |
| |
| struct CertificateAuthority { |
| const char* issuer; |
| const char* modulus; // In hex format. |
| }; |
| |
| const CertificateAuthority kKnownEndorsementCA[] = { |
| {"IFX TPM EK Intermediate CA 06", |
| "de9e58a353313d21d683c687d6aaaab240248717557c077161c5e515f41d8efa" |
| "48329f45658fb550f43f91d1ba0c2519429fb6ef964f89657098c90a9783ad6d" |
| "3baea625db044734c478768db53b6022c556d8174ed744bd6e4455665715cd5c" |
| "beb7c3fcb822ab3dfab1ecee1a628c3d53f6085983431598fb646f04347d5ae0" |
| "021d5757cc6e3027c1e13f10633ae48bbf98732c079c17684b0db58bd0291add" |
| "e277b037dd13fa3db910e81a4969622a79c85ac768d870f079b54c2b98c856e7" |
| "15ef0ba9c01ee1da1241838a1307fe94b1ddfa65cdf7eeaa7e5b4b8a94c3dcd0" |
| "29bb5ebcfc935e56641f4c8cb5e726c68f9dd6b41f8602ef6dc78d870a773571"}, |
| {"IFX TPM EK Intermediate CA 07", |
| "f04c9b5b9f3cbc2509179f5e0f31dceb302900f528458e002c3e914d6b29e5e0" |
| "924b0bcab2dd053f65d9d4a8eea8269c85c419dba640a88e14dc5f8c8c1a4269" |
| "7a5ac4594b36f923110f91d1803d385540c01a433140b06054c77a144ee3a6a6" |
| "5950c20f9215be3473b1002eb6b1756a22fbc18d21efacbbc8c270c66cf74982" |
| "e24f057825cab51c0dd840a4f2d059032239c33e3f52c6ca06fe49bf4f60cc28" |
| "a0fb1173d2ee05a141d30e8ffa32dbb86c1aeb5b309f76c2e462965612ec929a" |
| "0d3b04acfa4525912c76f765e948be71f505d619cc673a889f0ed9e1d75f237b" |
| "7af6a68550253cb4c3a8ff16c8091dbcbdea0ff8eee3d5bd92f49c53c5a15c93"}, |
| {"IFX TPM EK Intermediate CA 14", |
| "D5B2EB8F8F23DD0B5CA0C15D4376E27A0380FD8EB1E52C2C270D961E8C0F66FD" |
| "62E6ED6B3660FFBD8B0735179476F5E9C2EA4C762F5FEEDD3B5EB91785A724BC" |
| "4C0617B83966336DD9DC407640871BF99DF4E1701EB5A1F5647FC57879CBB973" |
| "B2A72BABA8536B2646A37AA5B73E32A4C8F03E35C8834B391AD363F1F7D1DF2B" |
| "EE39233F47384F3E2D2E8EF83C9539B4DFC360C8AEB88B6111E757AF646DC01A" |
| "68DAA908C7F8068894E9E991C59005068DD9B0F87113E6A80AB045DB4C1B23FF" |
| "38A106098C2E184E1CF42A43EA68753F2649999048E8A3C3406032BEB1457070" |
| "BCBE3A93E122638F6F18FF505C35FB827CE5D0C12F27F45C0F59C8A4A8697849"}, |
| {"IFX TPM EK Intermediate CA 16", |
| "B98D42D5284620036A6613ED05A1BE11431AE7DE435EC55F72814652B9265EC2" |
| "9035D401B538A9C84BB5B875450FAE8FBEDEF3430C4108D8516404F3DE4D4615" |
| "2F471013673A7C7F236304C7363B91C0E0FD9FC7A9EC751521A60A6042839CF7" |
| "7AEDE3243D0F51F47ACC39676D236BD5298E18B9A4783C60B2A1CD1B32124909" |
| "D5844649EE4539D6AA05A5902C147B4F062D5145708EAE224EC65A8B51D7A418" |
| "6327DA8F3B9E7C796F8B2DB3D2BDB39B829BDEBA8D2BF882CBADDB75D76FA8FA" |
| "313682688BCD2835533A3A68A4AFDF7E597D8B965402FF22A5A4A418FDB4B549" |
| "F218C3908E66BDCEAB3E2FE5EE0A4A1D9EB41A286ED07B6C112581FDAEA088D9"}, |
| {"IFX TPM EK Intermediate CA 17", |
| "B0F3CC6F02E8C0486501102731069644A815F631ED41676C05CE3F7E5E5E40DF" |
| "B3BF6D99787F2A9BE8F8B8035C03D5C2226072985230D4CE8407ACD6403F72E1" |
| "A4DBF069504E56FA8C0807A704526EAC1E379AE559EB4BBAD9DB4E652B3B14E5" |
| "38497A5E7768BCE0BFFAF800C61F1F2262775C526E1790A2BECF9A072A58F6A0" |
| "F3042B5279FE9957BCADC3C9725428B66B15D5263F00C528AC47716DE6938199" |
| "0FF23BC28F2C33B72D89B5F8EEEF9053B60D230431081D656EA8EC16C7CEFD9E" |
| "F5A9061A3C921394D453D9AC77397D59B4C3BAF258266F65559469C3007987D5" |
| "A8338E10FC54CD930303C37007D6E1E6C63F36BCFBA1E494AFB3ECD9A2407FF9"}, |
| {"IFX TPM EK Intermediate CA 21", |
| "8149397109974D6C0850C8A60304ED7D209B1B88F435B695394DAD9FB4E64180" |
| "02A3940966D2F04103C88659600EEA8E2A5C697C5F989F62D33A06DA10B50075" |
| "F37F3CE6AD070413A0E109E16FE652B393C4DAFC5579CCB9915E9A70F5C05BCE" |
| "0D341D6B887F43C4334BD8EC6A293FFAB737F77A45069CD0345D3D534E84D029" |
| "029C37A267C0CC2D8DCE3E2C76F21A40F5D8D463882A8CBB92D8235685266753" |
| "E8F051E78B681E87810A5B21EF719662A8208DFD94C55A126A112E39E0D732D7" |
| "3C599095FAFF52BBC0E8C5B3DCD904D05DE00D5C5112F3DF7B76602ABE5DC0F8" |
| "F89B55889A24C54EDBA1234AE498BE9B02CB5C8048D1DC90210705BAFC0E2837"}, |
| {"IFX TPM EK Intermediate CA 29", |
| "cd424370776890ace339c62d7faae843bb2c765d27685c0441d278361a929062" |
| "b4c95cc57213c864e91cbb92b1151f17a346a4e754c666f2a3e07ea9ffb9c80f" |
| "e54d9479f73458c64bf7b0ca4e38821dd318e82d6fe387903ca73ca3e59db48e" |
| "fe3b3c7c89599be87bb5e439a6f5843a412d4a321f154955448b71ca0b5fda47" |
| "5c86a1c999dde7a01aa16436e65f0b04874c0db3970546bd806157058c5576a5" |
| "c00b2bce7173c887f388dc4d5267c68fa5c47fcee3d8491071cd7742d43162cb" |
| "285f5ba5e0daa0e910fdce566c5bbf7b3701d51660090344195fd7278456bd98" |
| "48382fc5fceaebf93a2ec88c5722723519692e90d23f869c34d8b1af499d4127"}, |
| {"IFX TPM EK Intermediate CA 30", |
| "a01cc43c4b66076d483086d0713a336f435e33ed23d3cda05f3c60a6f707416a" |
| "9e53f0ef0de62c82a720e9ad94df29805b56b44279fd7389de4c60d498c81e3b" |
| "a27692a045d993e9aaae152768588e5c62213721154529c95b09b201bcb3e573" |
| "3d98e398d6e05215867d94e3d222e5b7df9f948c14533285821658b282be4bd7" |
| "fe7197baa642f556d4f18738adef26b2eebfc64045cf4c5dcbff661aa95429f4" |
| "e2c4921a8723bd8116f0efc038cd4530bb6e9299b7d70327e3fe8790d3d6db3a" |
| "ebd3ccd12aef3d43cf89463a28ad1306a9d430b08c3411bfeeda63b9fdcc9a23" |
| "1ff5cc203a7f5ee713d50e1930add1cd32ff64637fc740edb63380a5e6725381"}, |
| {"IFX TPM EK Intermediate CA 49", |
| "b0bd7dd4a197edae12edeb5c98a31f57af00142ca98ed9d412e1a1e8c3d1f81b" |
| "c152936ee6b1259cb49a870f358a7dca0c98d866df332727e6f897edcac5ea14" |
| "2ec2be2f0bb814a72d5986dead0ad20ecefa492966c1ca44fefb0533c311783c" |
| "d48d3f4027b996b6703d110a257d4bd0326f09e8f928020a6b953de4fb8f1dcb" |
| "ec3eaa6142f6068c38b4c8e41e85444965a04dfe64cc2ea1c09e374cfd1f4d4d" |
| "a76f31b57057ae79a803a8e96f5fd158920928ebcf1ff0fee75abce44ade9e71" |
| "56122cc4a11a4baa0ddf73f926ae58743b493d8c4bc8a393018041b543d8b223" |
| "d294de1d4fe8ec8f4d4e84646d1b6b78deadd34e507cccf472de1ca9ed0455bb"}, |
| {"NTC TPM EK Root CA 01", |
| "e836ac61b43e3252d5e1a8a4061997a6a0a272ba3d519d6be6360cc8b4b79e8c" |
| "d53c07a7ce9e9310ca84b82bbdad32184544ada357d458cf224c4a3130c97d00" |
| "4933b5db232d8b6509412eb4777e9e1b093c58b82b1679c84e57a6b218b4d61f" |
| "6dd4c3a66b2dd33b52cb1ffdff543289fa36dd71b7c83b66c1aae37caf7fe88d" |
| "851a3523e3ea92b59a6b0ca095c5e1d191484c1bff8a33048c3976e826d4c12a" |
| "e198f7199d183e0e70c8b46e8106edec3914397e051ae2b9a7f0b4bb9cd7f2ed" |
| "f71064eb0eb473df27b7ccef9a018d715c5fe6ab012a8315f933c7f4fc35d34c" |
| "efc27de224b2e3de3b3ba316d5df8b90b2eb879e219d270141b78dbb671a3a05"}, |
| {"STM TPM EK Intermediate CA 03", |
| "a5152b4fbd2c70c0c9a0dd919f48ddcde2b5c0c9988cff3b04ecd844f6cc0035" |
| "6c4e01b52463deb5179f36acf0c06d4574327c37572292fcd0f272c2d45ea7f2" |
| "2e8d8d18aa62354c279e03be9220f0c3822d16de1ea1c130b59afc56e08f22f1" |
| "902a07f881ebea3703badaa594ecbdf8fd1709211ba16769f73e76f348e2755d" |
| "bba2f94c1869ef71e726f56f8ece987f345c622e8b5c2a5466d41093c0dc2982" |
| "e6203d96f539b542347a08e87fc6e248a346d61a505f52add7f768a5203d70b8" |
| "68b6ec92ef7a83a4e6d1e1d259018705755d812175489fae83c4ab2957f69a99" |
| "9394ac7a243a5c1cd85f92b8648a8e0d23165fdd86fad06990bfd16fb3293379"}, |
| /* TODO(ngm): remove by: Aug 30 08:44:33 2020 GMT */ |
| {"CROS TPM DEV EK ROOT CA", |
| "cdc108745dc50dd6a1098c31486fb31578607fd64f64b0d91b994244ca1a9a69" |
| "a74c6bccc7f24923e1513e132dc0d9dbcb1b22089299bb6cb669cbf4b704c992" |
| "27bb769fa1f91ab11f67fb464a065b34b1a0e824136af5e59d1ac04bda22c199" |
| "9f7a5b34bd6b50c81b4a88cc097d4dfeb4dc695096463d9529d69f116e2a26de" |
| "070ef3118287072bdbe94466b8737049809bb8e1276b245930051b2bbbad71dd" |
| "20d26349d1d83cdb2ff9c65251a17dae4f400ecc3e77f89e27a75fe0709dc81f" |
| "e172008a3e65de685d9df43e036c557e88f1a9aedf7a91644391523d9728f946" |
| "45c0e8adaf37e9a15777021ad43b675583302402912d66233c59ad05fa3b34ed"}, |
| {"CROS TPM PRD EK ROOT CA", |
| "bd6f0198ffa7f7d20c15f81642096e335e2cd74734f73008265fc9957bbe018d" |
| "fbac0d2a0ea99f5fb7bbff6f0d367b81199e837c390527972aa5392c2ca0f2a3" |
| "506ee7d4a938f47158a7c56a390df2b781344a82b885a62f1de78f37ec105749" |
| "69d8abf3163f0cf5c67fa05dd4fb3eb07a7571888b7a87ed57735ce476156bf7" |
| "d6eff6cb8c8b303c21ebfe0e11b660edbdf903c70ac16927345d0b38c72f1e60" |
| "1460743584f5a3eaef303dbc5cfda48e4c7a1f338108c7f0c70a694f814b6691" |
| "ba9d058ab988152bb7097a010e400462187811c3e062001bce8aa808db485bd8" |
| "2f7f0e1e2a2ddb95c364dffea4c23e872fc3874c4756e85e6cf8eca6eb6a07bf"}, |
| }; |
| |
| const CertificateAuthority kKnownCrosCoreEndorsementCA[] = { |
| {"IFX TPM EK Intermediate CA 24", |
| "9D3F39677EBDB7B95F383021EA6EF90AD2BEA4E38B10CA65DCD84D0B33D400FA" |
| "E7E56FC553975FDADD425227F055C029B6544331E3BA50ED33F6CC02D833EA4E" |
| "0EECFE9AD1ADD7095F3A804C560F031E8705A3AD5189CBD62678B5B8205C37ED" |
| "780A3EDE8DE64A08980C048872E789937A49FC4048EADCAC9B3FD0F0DD085E76" |
| "30DDF9C0C31EFF3B77C6C3601AA7C3DCD10F08616C01435697746A61F920335C" |
| "0C45A41149F5D22FCD23DBE35003A9AF7FD91C18715E3709F86A38AB149113C4" |
| "D5273C3C90599734FF627ACBF408B082C76E486091F27446E175C50D340DA0FE" |
| "5C3FE3D590B8729F4E364E5BF7D854D9AE28EFBCD0CE8F19E6462B3A593983DF"}, |
| {"IFX TPM EK Intermediate CA 50", |
| "ACB01856664D0C81B545DB926D25019FC2D06B4A97DFB91FD7A5AB1A803AA6F4" |
| "12FEEE5E3DEF3634172F1271E893C6848B4D156485917DF6F0504947B39F0A5A" |
| "E14FFBAB9FF00E70448E51F11DEEA1EA16287ABAAE05D3D00FEB1AA064F1CBD9" |
| "E1E67C057087110F9D3023BFA0545C97BD51E473C5B183E50C2984BD9A2DA39B" |
| "7D028B895BD939FF0822595DDC948640D06E57ED72EF43B8D8071D2C3C0497A0" |
| "EC52F682D1637F06979733BAF56DD809D24C20354D73D3849A1C0DAD23AD5CCB" |
| "F8C679242D13FFFE055CC2AB2692897F0329EEA55AF3BB10A4EB4E2937601196" |
| "90D64FB352E3D34E05AB53BD4E01EFE3EF56F6DBE315B76A31B0100BF7096093"}, |
| }; |
| |
| // Returns a human-readable description for a known 3-byte |mode|. |
| std::string GetDescriptionForMode(const char* mode) { |
| return base::StringPrintf( |
| "Developer Mode: %s, Recovery Mode: %s, Firmware Type: %s", |
| mode[0] ? "On" : "Off", mode[1] ? "On" : "Off", |
| mode[2] ? "Verified" : "Developer"); |
| } |
| |
| std::string GetHardwareID() { |
| char buffer[VB_MAX_STRING_PROPERTY]; |
| const char* property = |
| VbGetSystemPropertyString("hwid", buffer, base::size(buffer)); |
| if (property != nullptr) { |
| return std::string(property); |
| } |
| LOG(WARNING) << "Could not read hwid property."; |
| return std::string(); |
| } |
| |
| // Finds CA by |issuer_name| and |is_cros_core| flag. On success returns true |
| // and fills |public_key_hex| with CA public key hex modulus. |
| bool GetAuthorityPublicKey(const std::string& issuer_name, |
| bool is_cros_core, |
| std::string* public_key_hex) { |
| const CertificateAuthority* const kKnownCA = |
| is_cros_core ? kKnownCrosCoreEndorsementCA : kKnownEndorsementCA; |
| const int kNumIssuers = is_cros_core ? base::size(kKnownCrosCoreEndorsementCA) |
| : base::size(kKnownEndorsementCA); |
| for (int i = 0; i < kNumIssuers; ++i) { |
| if (issuer_name == kKnownCA[i].issuer) { |
| public_key_hex->assign(kKnownCA[i].modulus); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::string GetACAName(attestation::ACAType aca_type) { |
| switch (aca_type) { |
| case attestation::DEFAULT_ACA: |
| return "the default ACA"; |
| case attestation::TEST_ACA: |
| return "the test ACA"; |
| default: { |
| std::ostringstream stream; |
| stream << "ACA " << aca_type; |
| return stream.str(); |
| } |
| } |
| } |
| |
| std::string GetIdentityFeaturesString(int identity_features) { |
| unsigned features_count = 0; |
| std::ostringstream stream; |
| if (identity_features == attestation::NO_IDENTITY_FEATURES) { |
| stream << "NO_IDENTITY_FEATURES"; |
| } else { |
| // We don't have reflection, copy/paste and adapt these few lines when |
| // adding a new enum value. |
| if (identity_features & |
| attestation::IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID) { |
| ++features_count; |
| if (stream.tellp() > 0) { |
| stream << ", "; |
| } |
| stream << "IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID"; |
| identity_features &= |
| ~attestation::IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID; |
| } |
| // Print other bits which may have been forgotten above. |
| if (identity_features) { |
| features_count += 2; // Forces plural. |
| if (stream.tellp() > 0) { |
| stream << ", "; |
| } |
| stream << "(undecoded features: " << identity_features << ")"; |
| } |
| } |
| return std::string("identity feature") + (features_count != 1 ? "s " : " ") + |
| stream.str(); |
| } |
| |
| std::string GetKeyTypeName(attestation::KeyType key_type) { |
| switch (key_type) { |
| case attestation::KEY_TYPE_ECC: |
| return "ECC"; |
| case attestation::KEY_TYPE_RSA: |
| return "RSA"; |
| } |
| return "unknown"; |
| } |
| |
| void LogErrorFromCA(const std::string& func, |
| const std::string& details, |
| const std::string& extra_details) { |
| std::ostringstream stream; |
| stream << func << ": Received error from Attestation CA"; |
| if (!details.empty()) { |
| stream << ": " << details; |
| if (!extra_details.empty()) { |
| stream << ". Extra details: " << extra_details; |
| } |
| } |
| LOG(ERROR) << stream.str() << "."; |
| } |
| |
| } // namespace |
| |
| namespace attestation { |
| |
| namespace { |
| |
| // Last PCR index to quote (we start at 0). |
| constexpr int kLastPcrToQuote = 1; |
| |
| pca_agent::EnrollRequest ToPcaAgentEnrollRequest( |
| const AttestationFlowData& data) { |
| pca_agent::EnrollRequest ret; |
| ret.set_aca_type(data.aca_type()); |
| ret.set_request(data.result_request()); |
| return ret; |
| } |
| |
| pca_agent::GetCertificateRequest ToPcaAgentCertRequest( |
| const AttestationFlowData& data) { |
| pca_agent::GetCertificateRequest ret; |
| ret.set_aca_type(data.aca_type()); |
| ret.set_request(data.result_request()); |
| return ret; |
| } |
| |
| } // namespace |
| |
| #if USE_TPM2 |
| |
| // Description of virtual NVRAM indices used for attestation. |
| const struct { |
| NVRAMQuoteType quote_type; |
| const char* quote_name; |
| uint32_t nv_index; // From CertifyNV(). |
| uint16_t nv_size; // From CertifyNV(). |
| } kNvramIndexData[] = { |
| {BOARD_ID, "BoardId", VIRTUAL_NV_INDEX_BOARD_ID, |
| VIRTUAL_NV_INDEX_BOARD_ID_SIZE}, |
| {SN_BITS, "SN Bits", VIRTUAL_NV_INDEX_SN_DATA, |
| VIRTUAL_NV_INDEX_SN_DATA_SIZE}, |
| {RSA_PUB_EK_CERT, "RSA Public EK Certificate", |
| trunks::kRsaEndorsementCertificateIndex, 0}, |
| {RSU_DEVICE_ID, "RSU Device ID", VIRTUAL_NV_INDEX_RSU_DEV_ID, |
| VIRTUAL_NV_INDEX_RSU_DEV_ID_SIZE}}; |
| |
| // Types of quotes being cached in the identity data. |
| const NVRAMQuoteType kNvramQuoteTypeInIdentityData[] = {BOARD_ID, SN_BITS}; |
| |
| // Types of quotes needed to obtain an enrollment certificate. |
| const NVRAMQuoteType kNvramQuoteTypeForEnrollmentCertificate[] = { |
| BOARD_ID, SN_BITS, RSU_DEVICE_ID}; |
| |
| #endif |
| |
| using QuoteMap = google::protobuf::Map<int, Quote>; |
| |
| const size_t kChallengeSignatureNonceSize = 20; // For all TPMs. |
| |
| AttestationService::AttestationService(brillo::SecureBlob* abe_data) |
| : abe_data_(abe_data), weak_factory_(this) {} |
| |
| bool AttestationService::Initialize() { |
| return InitializeWithCallback(base::DoNothing()); |
| } |
| |
| bool AttestationService::InitializeWithCallback( |
| InitializeCompleteCallback callback) { |
| if (!worker_thread_) { |
| worker_thread_.reset(new ServiceWorkerThread(this)); |
| worker_thread_->StartWithOptions( |
| base::Thread::Options(base::MessagePumpType::IO, 0)); |
| LOG(INFO) << "Attestation service started."; |
| } |
| // Creates |default_pca_agent_proxy_| here if needed; unlike other objects, |
| // |default_pca_agent_proxy_| is used in the origin thread instead of worker |
| // thread. |
| if (!pca_agent_proxy_) { |
| default_pca_agent_proxy_ = |
| attestation::pca_agent::client::CreateWithDBusTaskRunner( |
| worker_thread_->task_runner()); |
| pca_agent_proxy_ = default_pca_agent_proxy_.get(); |
| } |
| worker_thread_->task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&AttestationService::InitializeTask, |
| base::Unretained(this), std::move(callback))); |
| return true; |
| } |
| |
| void AttestationService::InitializeTask(InitializeCompleteCallback callback) { |
| if (!tpm_utility_) { |
| default_tpm_utility_.reset(TpmUtilityFactory::New()); |
| CHECK(default_tpm_utility_->Initialize()); |
| tpm_utility_ = default_tpm_utility_.get(); |
| } |
| if (!crypto_utility_) { |
| default_crypto_utility_.reset(new CryptoUtilityImpl(tpm_utility_)); |
| crypto_utility_ = default_crypto_utility_.get(); |
| } |
| #if USE_TPM2 |
| for (int i = 0; i < base::size(kNvramIndexData); ++i) { |
| nvram_quote_type_to_index_data_[kNvramIndexData[i].quote_type] = i; |
| } |
| #endif |
| bool existing_database; |
| if (database_) { |
| existing_database = true; |
| } else { |
| default_database_.reset(new DatabaseImpl(crypto_utility_, tpm_utility_)); |
| existing_database = default_database_->Initialize(); |
| database_ = default_database_.get(); |
| } |
| if (existing_database && MigrateAttestationDatabase()) { |
| if (!database_->SaveChanges()) { |
| LOG(WARNING) << "Attestation: Failed to persist database changes."; |
| } |
| } |
| if (!key_store_) { |
| pkcs11_token_manager_.reset(new chaps::TokenManagerClient()); |
| default_key_store_.reset(new Pkcs11KeyStore(pkcs11_token_manager_.get())); |
| key_store_ = default_key_store_.get(); |
| } |
| if (hwid_.empty()) { |
| hwid_ = GetHardwareID(); |
| } |
| if (!IsPreparedForEnrollment()) { |
| worker_thread_->task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&AttestationService::PrepareForEnrollment, |
| base::Unretained(this), std::move(callback))); |
| } else { |
| // Ignore errors. If failed this time, will be re-attempted on next boot. |
| tpm_utility_->RemoveOwnerDependency(); |
| std::move(callback).Run(false); |
| } |
| } |
| |
| bool AttestationService::MigrateAttestationDatabase() { |
| bool migrated = false; |
| |
| auto* database_pb = database_->GetMutableProtobuf(); |
| if (database_pb->has_credentials()) { |
| if (!database_pb->credentials().encrypted_endorsement_credentials().count( |
| DEFAULT_ACA) && |
| database_pb->credentials() |
| .has_default_encrypted_endorsement_credential()) { |
| LOG(INFO) << "Attestation: Migrating endorsement credential for " |
| << GetACAName(DEFAULT_ACA) << "."; |
| (*database_pb->mutable_credentials() |
| ->mutable_encrypted_endorsement_credentials())[DEFAULT_ACA] = |
| database_pb->credentials().default_encrypted_endorsement_credential(); |
| migrated = true; |
| } |
| if (!database_pb->credentials().encrypted_endorsement_credentials().count( |
| TEST_ACA) && |
| database_pb->credentials() |
| .has_test_encrypted_endorsement_credential()) { |
| LOG(INFO) << "Attestation: Migrating endorsement credential for " |
| << GetACAName(TEST_ACA) << "."; |
| (*database_pb->mutable_credentials() |
| ->mutable_encrypted_endorsement_credentials())[TEST_ACA] = |
| database_pb->credentials().test_encrypted_endorsement_credential(); |
| migrated = true; |
| } |
| } |
| |
| // Migrate identity data if needed. |
| migrated |= MigrateIdentityData(); |
| |
| if (migrated) { |
| EncryptAllEndorsementCredentials(); |
| LOG(INFO) << "Attestation: Migrated attestation database."; |
| } |
| |
| // Migrate Rsa PublicKey Format to SubjectPublicKeyInfo |
| if (database_pb->credentials().has_legacy_endorsement_public_key()) { |
| std::string public_key_info; |
| if (GetSubjectPublicKeyInfo( |
| database_pb->credentials().endorsement_key_type(), |
| database_pb->credentials().legacy_endorsement_public_key(), |
| &public_key_info)) { |
| database_pb->mutable_credentials()->set_endorsement_public_key( |
| public_key_info); |
| } else { |
| // If the format conversion fails, that means the EK public key is broken |
| // somehow, which should not happen. If it does, that means EK data |
| // becomes invalid. Clean up all EK metadata to resolve this problem. |
| LOG(ERROR) << __func__ << ": Migrate public format fail."; |
| database_pb->mutable_credentials()->clear_endorsement_key_type(); |
| database_pb->mutable_credentials()->clear_endorsement_credential(); |
| } |
| database_pb->mutable_credentials()->clear_legacy_endorsement_public_key(); |
| migrated |= true; |
| } |
| |
| return migrated; |
| } |
| |
| bool AttestationService::MigrateIdentityData() { |
| auto* database_pb = database_->GetMutableProtobuf(); |
| if (database_pb->identities().size() > 0) { |
| // We already migrated identity data. |
| return false; |
| } |
| |
| bool error = false; |
| |
| // The identity we're creating will have the next index in identities. |
| LOG(INFO) << "Attestation: Migrating existing identity into identity " |
| << database_pb->identities().size() << "."; |
| CHECK(database_pb->identities().size() == kFirstIdentity); |
| AttestationDatabase::Identity* identity_data = |
| database_pb->mutable_identities()->Add(); |
| identity_data->set_features(IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID); |
| if (database_pb->has_identity_binding()) { |
| identity_data->mutable_identity_binding()->CopyFrom( |
| database_pb->identity_binding()); |
| } |
| if (database_pb->has_identity_key()) { |
| identity_data->mutable_identity_key()->CopyFrom( |
| database_pb->identity_key()); |
| identity_data->mutable_identity_key()->clear_identity_credential(); |
| if (database_pb->identity_key().has_identity_credential()) { |
| // Create an identity certificate for this identity and the default ACA. |
| AttestationDatabase::IdentityCertificate identity_certificate; |
| identity_certificate.set_identity(kFirstIdentity); |
| identity_certificate.set_aca(DEFAULT_ACA); |
| identity_certificate.set_identity_credential( |
| database_pb->identity_key().identity_credential()); |
| auto* map = database_pb->mutable_identity_certificates(); |
| auto in = map->insert(IdentityCertificateMap::value_type( |
| DEFAULT_ACA, identity_certificate)); |
| if (!in.second) { |
| LOG(ERROR) << "Attestation: Could not migrate existing identity."; |
| error = true; |
| } |
| } |
| if (database_pb->identity_key().has_enrollment_id()) { |
| database_->GetMutableProtobuf()->set_enrollment_id( |
| database_pb->identity_key().enrollment_id()); |
| } |
| } |
| |
| if (database_pb->has_pcr0_quote()) { |
| auto in = identity_data->mutable_pcr_quotes()->insert( |
| QuoteMap::value_type(0, database_pb->pcr0_quote())); |
| if (!in.second) { |
| LOG(ERROR) << "Attestation: Could not migrate existing identity."; |
| error = true; |
| } |
| } else { |
| LOG(ERROR) << "Attestation: Missing PCR0 quote in existing database."; |
| error = true; |
| } |
| if (database_pb->has_pcr1_quote()) { |
| auto in = identity_data->mutable_pcr_quotes()->insert( |
| QuoteMap::value_type(1, database_pb->pcr1_quote())); |
| if (!in.second) { |
| LOG(ERROR) << "Attestation: Could not migrate existing identity."; |
| error = true; |
| } |
| } else { |
| LOG(ERROR) << "Attestation: Missing PCR1 quote in existing database."; |
| error = true; |
| } |
| |
| if (error) { |
| database_pb->mutable_identities()->RemoveLast(); |
| database_pb->mutable_identity_certificates()->erase(DEFAULT_ACA); |
| } |
| |
| return !error; |
| } |
| |
| void AttestationService::ShutdownTask() { |
| database_ = nullptr; |
| default_database_.reset(nullptr); |
| crypto_utility_ = nullptr; |
| default_crypto_utility_.reset(nullptr); |
| tpm_utility_ = nullptr; |
| default_tpm_utility_.reset(nullptr); |
| } |
| |
| void AttestationService::GetKeyInfo(const GetKeyInfoRequest& request, |
| const GetKeyInfoCallback& callback) { |
| auto result = std::make_shared<GetKeyInfoReply>(); |
| base::Closure task = base::Bind(&AttestationService::GetKeyInfoTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<GetKeyInfoReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::GetKeyInfoTask( |
| const GetKeyInfoRequest& request, |
| const std::shared_ptr<GetKeyInfoReply>& result) { |
| CertifiedKey key; |
| if (!FindKeyByLabel(request.username(), request.key_label(), &key)) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo(key.key_type(), key.public_key(), |
| &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Bad public key."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_key_type(key.key_type()); |
| result->set_key_usage(key.key_usage()); |
| result->set_public_key(public_key_info); |
| result->set_certify_info(key.certified_key_info()); |
| result->set_certify_info_signature(key.certified_key_proof()); |
| if (key.has_intermediate_ca_cert()) { |
| result->set_certificate(CreatePEMCertificateChain(key)); |
| } else { |
| result->set_certificate(key.certified_key_credential()); |
| } |
| result->set_payload(key.payload()); |
| } |
| |
| void AttestationService::GetEndorsementInfo( |
| const GetEndorsementInfoRequest& request, |
| const GetEndorsementInfoCallback& callback) { |
| auto result = std::make_shared<GetEndorsementInfoReply>(); |
| base::Closure task = base::Bind(&AttestationService::GetEndorsementInfoTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<GetEndorsementInfoReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| base::Optional<std::string> AttestationService::GetEndorsementPublicKey() |
| const { |
| const auto& database_pb = database_->GetProtobuf(); |
| if (database_pb.has_credentials() && |
| database_pb.credentials().has_endorsement_public_key()) { |
| return database_pb.credentials().endorsement_public_key(); |
| } |
| |
| // Try to read the public key directly. |
| std::string public_key; |
| if (!tpm_utility_->GetEndorsementPublicKey(GetEndorsementKeyType(), |
| &public_key)) { |
| return base::nullopt; |
| } |
| return public_key; |
| } |
| |
| base::Optional<std::string> AttestationService::GetEndorsementCertificate() |
| const { |
| const auto& database_pb = database_->GetProtobuf(); |
| if (database_pb.has_credentials() && |
| database_pb.credentials().has_endorsement_credential()) { |
| return database_pb.credentials().endorsement_credential(); |
| } |
| |
| // Try to read the certificate directly. |
| std::string certificate; |
| if (!tpm_utility_->GetEndorsementCertificate(GetEndorsementKeyType(), |
| &certificate)) { |
| return base::nullopt; |
| } |
| return certificate; |
| } |
| |
| void AttestationService::GetEndorsementInfoTask( |
| const GetEndorsementInfoRequest& request, |
| const std::shared_ptr<GetEndorsementInfoReply>& result) { |
| const auto& key_type = GetEndorsementKeyType(); |
| |
| if (key_type != KEY_TYPE_RSA && key_type != KEY_TYPE_ECC) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| |
| base::Optional<std::string> public_key = GetEndorsementPublicKey(); |
| if (!public_key.has_value()) { |
| LOG(ERROR) << __func__ << ": Endorsement public key not available."; |
| result->set_status(STATUS_NOT_AVAILABLE); |
| return; |
| } |
| |
| base::Optional<std::string> certificate = GetEndorsementCertificate(); |
| if (!certificate.has_value()) { |
| LOG(ERROR) << __func__ << ": Endorsement cert not available."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| |
| result->set_ek_public_key(public_key.value()); |
| result->set_ek_certificate(certificate.value()); |
| std::string hash = crypto::SHA256HashString(certificate.value()); |
| result->set_ek_info( |
| base::StringPrintf("EK Certificate:\n%s\nHash:\n%s\n", |
| CreatePEMCertificate(certificate.value()).c_str(), |
| base::HexEncode(hash.data(), hash.size()).c_str())); |
| } |
| |
| void AttestationService::GetAttestationKeyInfo( |
| const GetAttestationKeyInfoRequest& request, |
| const GetAttestationKeyInfoCallback& callback) { |
| auto result = std::make_shared<GetAttestationKeyInfoReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::GetAttestationKeyInfoTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<GetAttestationKeyInfoReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::GetAttestationKeyInfoTask( |
| const GetAttestationKeyInfoRequest& request, |
| const std::shared_ptr<GetAttestationKeyInfoReply>& result) { |
| const int identity = kFirstIdentity; |
| auto aca_type = request.aca_type(); |
| auto found = FindIdentityCertificate(identity, aca_type); |
| if (found == |
| database_->GetMutableProtobuf()->mutable_identity_certificates()->end()) { |
| LOG(ERROR) << __func__ << ": Identity " << identity |
| << " is not enrolled for attestation with " |
| << GetACAName(aca_type) << "."; |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| const auto& identity_certificate = found->second; |
| if (!IsPreparedForEnrollment() || |
| identity_certificate.identity() >= |
| database_->GetProtobuf().identities().size()) { |
| result->set_status(STATUS_NOT_AVAILABLE); |
| return; |
| } |
| const auto& identity_pb = database_->GetProtobuf().identities().Get( |
| identity_certificate.identity()); |
| if (!identity_pb.has_identity_key()) { |
| result->set_status(STATUS_NOT_AVAILABLE); |
| return; |
| } |
| if (identity_pb.identity_key().has_identity_public_key_der()) { |
| // TODO(crbug/942487): Use SubjectPublicKeyInfo for identity_public_key_der |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo( |
| identity_pb.identity_key().identity_key_type(), |
| identity_pb.identity_key().identity_public_key_der(), |
| &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Bad public key."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_public_key(public_key_info); |
| } |
| if (identity_pb.has_identity_binding() && |
| identity_pb.identity_binding().has_identity_public_key_tpm_format()) { |
| result->set_public_key_tpm_format( |
| identity_pb.identity_binding().identity_public_key_tpm_format()); |
| } |
| if (identity_certificate.has_identity_credential()) { |
| result->set_certificate(identity_certificate.identity_credential()); |
| } |
| if (identity_pb.pcr_quotes().count(0)) { |
| *result->mutable_pcr0_quote() = identity_pb.pcr_quotes().at(0); |
| } |
| if (identity_pb.pcr_quotes().count(1)) { |
| *result->mutable_pcr1_quote() = identity_pb.pcr_quotes().at(1); |
| } |
| } |
| |
| void AttestationService::ActivateAttestationKey( |
| const ActivateAttestationKeyRequest& request, |
| const ActivateAttestationKeyCallback& callback) { |
| auto result = std::make_shared<ActivateAttestationKeyReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::ActivateAttestationKeyTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<ActivateAttestationKeyReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::ActivateAttestationKeyTask( |
| const ActivateAttestationKeyRequest& request, |
| const std::shared_ptr<ActivateAttestationKeyReply>& result) { |
| if (request.encrypted_certificate().tpm_version() != |
| tpm_utility_->GetVersion()) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| LOG(ERROR) << __func__ << ": TPM version mismatch."; |
| return; |
| } |
| std::string certificate; |
| if (!ActivateAttestationKeyInternal( |
| kFirstIdentity, request.aca_type(), GetEndorsementKeyType(), |
| request.encrypted_certificate(), request.save_certificate(), |
| &certificate, nullptr /* certificate_index */)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_certificate(certificate); |
| } |
| |
| void AttestationService::CreateCertifiableKey( |
| const CreateCertifiableKeyRequest& request, |
| const CreateCertifiableKeyCallback& callback) { |
| auto result = std::make_shared<CreateCertifiableKeyReply>(); |
| base::Closure task = base::Bind(&AttestationService::CreateCertifiableKeyTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<CreateCertifiableKeyReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::CreateCertifiableKeyTask( |
| const CreateCertifiableKeyRequest& request, |
| const std::shared_ptr<CreateCertifiableKeyReply>& result) { |
| CertifiedKey key; |
| if (!CreateKey(request.username(), request.key_label(), request.key_type(), |
| request.key_usage(), &key)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo(key.key_type(), key.public_key(), |
| &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Bad public key."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_public_key(public_key_info); |
| result->set_certify_info(key.certified_key_info()); |
| result->set_certify_info_signature(key.certified_key_proof()); |
| } |
| |
| void AttestationService::Decrypt(const DecryptRequest& request, |
| const DecryptCallback& callback) { |
| auto result = std::make_shared<DecryptReply>(); |
| base::Closure task = base::Bind(&AttestationService::DecryptTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<DecryptReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::DecryptTask( |
| const DecryptRequest& request, |
| const std::shared_ptr<DecryptReply>& result) { |
| CertifiedKey key; |
| if (!FindKeyByLabel(request.username(), request.key_label(), &key)) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| std::string data; |
| if (!tpm_utility_->Unbind(key.key_blob(), request.encrypted_data(), &data)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_decrypted_data(data); |
| } |
| |
| void AttestationService::Sign(const SignRequest& request, |
| const SignCallback& callback) { |
| auto result = std::make_shared<SignReply>(); |
| base::Closure task = base::Bind(&AttestationService::SignTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<SignReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::SignTask(const SignRequest& request, |
| const std::shared_ptr<SignReply>& result) { |
| CertifiedKey key; |
| if (!FindKeyByLabel(request.username(), request.key_label(), &key)) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| std::string signature; |
| if (!tpm_utility_->Sign(key.key_blob(), request.data_to_sign(), &signature)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_signature(signature); |
| } |
| |
| void AttestationService::RegisterKeyWithChapsToken( |
| const RegisterKeyWithChapsTokenRequest& request, |
| const RegisterKeyWithChapsTokenCallback& callback) { |
| auto result = std::make_shared<RegisterKeyWithChapsTokenReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::RegisterKeyWithChapsTokenTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<RegisterKeyWithChapsTokenReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::RegisterKeyWithChapsTokenTask( |
| const RegisterKeyWithChapsTokenRequest& request, |
| const std::shared_ptr<RegisterKeyWithChapsTokenReply>& result) { |
| CertifiedKey key; |
| if (!FindKeyByLabel(request.username(), request.key_label(), &key)) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| std::string certificate; |
| if (request.include_certificates()) { |
| certificate = key.certified_key_credential(); |
| } |
| if (!key_store_->Register(request.username(), request.key_label(), |
| key.key_type(), key.key_usage(), key.key_blob(), |
| key.public_key(), certificate)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| if (request.include_certificates()) { |
| if (key.has_intermediate_ca_cert() && |
| !key_store_->RegisterCertificate(request.username(), |
| key.intermediate_ca_cert())) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| for (int i = 0; i < key.additional_intermediate_ca_cert_size(); ++i) { |
| if (!key_store_->RegisterCertificate( |
| request.username(), key.additional_intermediate_ca_cert(i))) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| } |
| } |
| DeleteKey(request.username(), request.key_label()); |
| } |
| |
| bool AttestationService::IsPreparedForEnrollment() { |
| if (!tpm_utility_->IsTpmReady()) { |
| return false; |
| } |
| const auto& database_pb = database_->GetProtobuf(); |
| // Note that this function only checks for the existence of endorsement |
| // credentials, but the identity key, identity key binding and pcr quotes |
| // signed by the identity key are also required for enrollment. |
| // In normal circumstances, existence of the endorsement credentials implies |
| // the existence of the other identity key related pieces, but it is |
| // possible for that not to be true, for instance, see crbug.com/899932 |
| return database_pb.credentials().has_endorsement_credential() || |
| database_pb.credentials().encrypted_endorsement_credentials().size() > |
| TEST_ACA; |
| } |
| |
| bool AttestationService::IsPreparedForEnrollmentWithACA(ACAType aca_type) { |
| const auto& database_pb = database_->GetProtobuf(); |
| return database_pb.credentials().encrypted_endorsement_credentials().count( |
| aca_type); |
| } |
| bool AttestationService::IsEnrolled() { |
| return IsEnrolledWithACA(DEFAULT_ACA) || IsEnrolledWithACA(TEST_ACA); |
| } |
| |
| bool AttestationService::IsEnrolledWithACA(ACAType aca_type) { |
| return HasIdentityCertificate(kFirstIdentity, aca_type); |
| } |
| |
| AttestationService::IdentityCertificateMap::iterator |
| AttestationService::FindIdentityCertificate(int identity, ACAType aca_type) { |
| auto* database_pb = database_->GetMutableProtobuf(); |
| auto end = database_pb->mutable_identity_certificates()->end(); |
| for (auto it = database_pb->mutable_identity_certificates()->begin(); |
| it != end; ++it) { |
| if (it->second.identity() == identity && it->second.aca() == aca_type) { |
| return it; |
| } |
| } |
| return end; |
| } |
| |
| AttestationDatabase_IdentityCertificate* |
| AttestationService::FindOrCreateIdentityCertificate(int identity, |
| ACAType aca_type, |
| int* cert_index) { |
| // Find an identity certificate to reuse or create a new one. |
| int index; |
| auto* database_pb = database_->GetMutableProtobuf(); |
| auto found = FindIdentityCertificate(identity, aca_type); |
| if (found == database_pb->mutable_identity_certificates()->end()) { |
| index = identity == kFirstIdentity |
| ? aca_type |
| : std::max(static_cast<size_t>(kMaxACATypeInternal), |
| database_pb->identity_certificates().size()); |
| AttestationDatabase::IdentityCertificate new_identity_certificate; |
| new_identity_certificate.set_identity(identity); |
| new_identity_certificate.set_aca(aca_type); |
| auto* map = database_pb->mutable_identity_certificates(); |
| auto in = map->insert( |
| IdentityCertificateMap::value_type(index, new_identity_certificate)); |
| if (!in.second) { |
| LOG(ERROR) << __func__ << ": Failed to create identity certificate " |
| << index << " for identity " << identity << " and " |
| << GetACAName(aca_type) << "."; |
| if (cert_index) { |
| *cert_index = -1; |
| } |
| return nullptr; |
| } |
| found = in.first; |
| LOG(INFO) << "Attestation: Creating identity certificate " << index |
| << " for identity " << identity << " enrolled with " |
| << GetACAName(aca_type); |
| } else { |
| index = found->first; |
| } |
| if (cert_index) { |
| *cert_index = index; |
| } |
| return &found->second; |
| } |
| |
| bool AttestationService::HasIdentityCertificate(int identity, |
| ACAType aca_type) { |
| return FindIdentityCertificate(identity, aca_type) != |
| database_->GetMutableProtobuf() |
| ->mutable_identity_certificates() |
| ->end(); |
| } |
| |
| bool AttestationService::CreateEnrollRequestInternal( |
| ACAType aca_type, std::string* enroll_request) { |
| const int identity = kFirstIdentity; |
| if (!IsPreparedForEnrollmentWithACA(aca_type)) { |
| LOG(ERROR) << __func__ << ": Enrollment with " << GetACAName(aca_type) |
| << " is not possible, attestation data does not exist."; |
| return false; |
| } |
| const auto& database_pb = database_->GetProtobuf(); |
| if (database_pb.identities().size() <= identity) { |
| LOG(ERROR) << __func__ << ": Enrollment with " << GetACAName(aca_type) |
| << " is not possible, identity " << identity |
| << " does not exist."; |
| return false; |
| } |
| AttestationEnrollmentRequest request_pb; |
| request_pb.set_tpm_version(tpm_utility_->GetVersion()); |
| *request_pb.mutable_encrypted_endorsement_credential() = |
| database_pb.credentials().encrypted_endorsement_credentials().at( |
| aca_type); |
| const AttestationDatabase::Identity& identity_data = |
| database_pb.identities().Get(identity); |
| request_pb.set_identity_public_key( |
| identity_data.identity_binding().identity_public_key_tpm_format()); |
| *request_pb.mutable_pcr0_quote() = identity_data.pcr_quotes().at(0); |
| *request_pb.mutable_pcr1_quote() = identity_data.pcr_quotes().at(1); |
| |
| if (identity_data.features() & IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID) { |
| std::string enterprise_enrollment_nonce = |
| ComputeEnterpriseEnrollmentNonce(); |
| if (!enterprise_enrollment_nonce.empty()) { |
| request_pb.set_enterprise_enrollment_nonce( |
| enterprise_enrollment_nonce.data(), |
| enterprise_enrollment_nonce.size()); |
| } |
| |
| if (GetEndorsementKeyType() != KEY_TYPE_RSA) { |
| // Include an encrypted quote of the RSA pub EK certificate so that |
| // an EID can be computed during enrollment. |
| |
| auto found = identity_data.nvram_quotes().find(RSA_PUB_EK_CERT); |
| if (found == identity_data.nvram_quotes().end()) { |
| LOG(ERROR) << __func__ |
| << ": Cannot find RSA pub EK certificate quote in identity " |
| << identity << "."; |
| return false; |
| } |
| |
| std::string serialized_quote; |
| if (!found->second.SerializeToString(&serialized_quote)) { |
| LOG(ERROR) << __func__ |
| << ": Failed to serialize RSA pub EK quote protobuf."; |
| return false; |
| } |
| if (!EncryptDataForAttestationCA( |
| aca_type, serialized_quote, |
| request_pb.mutable_encrypted_rsa_endorsement_quote())) { |
| LOG(ERROR) |
| << "Attestation: Failed to encrypt RSA pub EK certificate for " |
| << GetACAName(aca_type) << "."; |
| return false; |
| } |
| } |
| } |
| |
| if (!request_pb.SerializeToString(enroll_request)) { |
| LOG(ERROR) << __func__ << ": Failed to serialize protobuf."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool AttestationService::FinishEnrollInternal( |
| ACAType aca_type, |
| const std::string& enroll_response, |
| std::string* server_error) { |
| const int identity = kFirstIdentity; |
| if (!tpm_utility_->IsTpmReady()) { |
| LOG(ERROR) << __func__ |
| << ": Cannot finish enrollment as the TPM is not ready."; |
| return false; |
| } |
| AttestationEnrollmentResponse response_pb; |
| if (!response_pb.ParseFromString(enroll_response)) { |
| LOG(ERROR) << __func__ << ": Failed to parse response from CA."; |
| return false; |
| } |
| if (response_pb.status() != OK) { |
| *server_error = response_pb.detail(); |
| LogErrorFromCA(__func__, response_pb.detail(), response_pb.extra_details()); |
| return false; |
| } |
| if (response_pb.encrypted_identity_credential().tpm_version() != |
| tpm_utility_->GetVersion()) { |
| LOG(ERROR) << __func__ << ": TPM version mismatch."; |
| return false; |
| } |
| int certificate_index; |
| if (!ActivateAttestationKeyInternal( |
| identity, aca_type, GetEndorsementKeyType(), |
| response_pb.encrypted_identity_credential(), |
| true /* save_certificate */, nullptr /* certificate */, |
| &certificate_index)) { |
| return false; |
| } |
| LOG(INFO) << __func__ << ": Enrollment of identity " << identity << " with " |
| << GetACAName(aca_type) << " complete. Certificate #" |
| << certificate_index << "."; |
| return true; |
| } |
| |
| bool AttestationService::CreateCertificateRequestInternal( |
| ACAType aca_type, |
| const std::string& username, |
| const CertifiedKey& key, |
| CertificateProfile profile, |
| const std::string& origin, |
| std::string* certificate_request, |
| std::string* message_id) { |
| if (!tpm_utility_->IsTpmReady()) { |
| return false; |
| } |
| if (!IsEnrolledWithACA(aca_type)) { |
| LOG(ERROR) << __func__ << ": Device is not enrolled for attestation with " |
| << GetACAName(aca_type) << "."; |
| return false; |
| } |
| auto found = FindIdentityCertificate(kFirstIdentity, aca_type); |
| if (found == |
| database_->GetMutableProtobuf()->mutable_identity_certificates()->end()) { |
| LOG(ERROR) << __func__ << ": Identity " << kFirstIdentity |
| << " is not enrolled for attestation with " |
| << GetACAName(aca_type) << "."; |
| return false; |
| } |
| const auto& identity_certificate = found->second; |
| if (!crypto_utility_->GetRandom(kNonceSize, message_id)) { |
| LOG(ERROR) << __func__ << ": GetRandom(message_id) failed."; |
| return false; |
| } |
| AttestationCertificateRequest request_pb; |
| request_pb.set_tpm_version(tpm_utility_->GetVersion()); |
| request_pb.set_message_id(*message_id); |
| request_pb.set_identity_credential( |
| identity_certificate.identity_credential()); |
| request_pb.set_profile(profile); |
| |
| #if USE_TPM2 |
| |
| if (profile == ENTERPRISE_ENROLLMENT_CERTIFICATE) { |
| const int identity = identity_certificate.identity(); |
| const AttestationDatabase::Identity& identity_data = |
| database_->GetProtobuf().identities().Get(identity); |
| // Copy NVRAM quotes to include in an enrollment certificate from the |
| // identity if possible. |
| std::set<NVRAMQuoteType> not_in_identity; |
| for (int i = 0; i < base::size(kNvramQuoteTypeForEnrollmentCertificate); |
| ++i) { |
| const auto quote_type = kNvramQuoteTypeForEnrollmentCertificate[i]; |
| const auto found = identity_data.nvram_quotes().find(quote_type); |
| if (found != identity_data.nvram_quotes().cend()) { |
| (*request_pb.mutable_nvram_quotes())[quote_type] = found->second; |
| } else { |
| not_in_identity.insert(quote_type); |
| } |
| } |
| // Data that is supposed to be in the identiy but is missing won't be |
| // quoted now, as we want to drive everything from the identity. |
| for (int i = 0; i < base::size(kNvramQuoteTypeInIdentityData); ++i) { |
| const auto quote_type = kNvramQuoteTypeInIdentityData[i]; |
| if (not_in_identity.erase(quote_type)) { |
| LOG(WARNING) |
| << "Could not find " |
| << kNvramIndexData[nvram_quote_type_to_index_data_[quote_type]] |
| .quote_name |
| << " quote in identity " << identity |
| << " to provide in enrollment cert request."; |
| } |
| } |
| // Quote the other data now. |
| for (auto it = not_in_identity.cbegin(), end = not_in_identity.cend(); |
| it != end; ++it) { |
| Quote quote; |
| if (QuoteNvramData(*it, identity_data.identity_key(), "e)) { |
| (*request_pb.mutable_nvram_quotes())[*it] = quote; |
| } else { |
| LOG(WARNING) |
| << "Could not provide " |
| << kNvramIndexData[nvram_quote_type_to_index_data_[*it]].quote_name |
| << " quote in enrollment cert request."; |
| } |
| } |
| } |
| |
| #endif |
| |
| if (!origin.empty() && |
| (profile == CONTENT_PROTECTION_CERTIFICATE_WITH_STABLE_ID)) { |
| request_pb.set_origin(origin); |
| request_pb.set_temporal_index(ChooseTemporalIndex(username, origin)); |
| } |
| request_pb.set_certified_public_key(key.public_key_tpm_format()); |
| request_pb.set_certified_key_info(key.certified_key_info()); |
| request_pb.set_certified_key_proof(key.certified_key_proof()); |
| if (!request_pb.SerializeToString(certificate_request)) { |
| LOG(ERROR) << __func__ << ": Failed to serialize protobuf."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool AttestationService::FinishCertificateRequestInternal( |
| const std::string& certificate_response, |
| const std::string& username, |
| const std::string& key_label, |
| const std::string& message_id, |
| CertifiedKey* key, |
| std::string* certificate_chain, |
| std::string* server_error) { |
| if (!tpm_utility_->IsTpmReady()) { |
| return false; |
| } |
| AttestationCertificateResponse response_pb; |
| if (!response_pb.ParseFromString(certificate_response)) { |
| LOG(ERROR) << __func__ << ": Failed to parse response from Attestation CA."; |
| return false; |
| } |
| if (response_pb.status() != OK) { |
| *server_error = response_pb.detail(); |
| LogErrorFromCA(__func__, response_pb.detail(), response_pb.extra_details()); |
| return false; |
| } |
| if (message_id != response_pb.message_id()) { |
| LOG(ERROR) << __func__ << ": Message ID mismatch."; |
| return false; |
| } |
| return PopulateAndStoreCertifiedKey(response_pb, username, key_label, key, |
| certificate_chain); |
| } |
| |
| bool AttestationService::PopulateAndStoreCertifiedKey( |
| const AttestationCertificateResponse& response_pb, |
| const std::string& username, |
| const std::string& key_label, |
| CertifiedKey* key, |
| std::string* certificate_chain) { |
| // Finish populating the CertifiedKey protobuf and store it. |
| key->set_key_name(key_label); |
| key->set_certified_key_credential(response_pb.certified_key_credential()); |
| key->set_intermediate_ca_cert(response_pb.intermediate_ca_cert()); |
| key->mutable_additional_intermediate_ca_cert()->MergeFrom( |
| response_pb.additional_intermediate_ca_cert()); |
| if (!SaveKey(username, key_label, *key)) { |
| return false; |
| } |
| LOG(INFO) << "Attestation: Certified key credential received and stored."; |
| *certificate_chain = CreatePEMCertificateChain(*key); |
| return true; |
| } |
| |
| bool AttestationService::FindKeyByLabel(const std::string& username, |
| const std::string& key_label, |
| CertifiedKey* key) { |
| if (!username.empty()) { |
| std::string key_data; |
| if (!key_store_->Read(username, key_label, &key_data)) { |
| LOG(INFO) << "Key not found: " << key_label; |
| return false; |
| } |
| if (key && !key->ParseFromString(key_data)) { |
| LOG(ERROR) << "Failed to parse key: " << key_label; |
| return false; |
| } |
| return true; |
| } |
| auto database_pb = database_->GetProtobuf(); |
| for (int i = 0; i < database_pb.device_keys_size(); ++i) { |
| if (database_pb.device_keys(i).key_name() == key_label) { |
| *key = database_pb.device_keys(i); |
| return true; |
| } |
| } |
| LOG(INFO) << "Key not found: " << key_label; |
| return false; |
| } |
| |
| bool AttestationService::CreateKey(const std::string& username, |
| const std::string& key_label, |
| KeyType key_type, |
| KeyUsage key_usage, |
| CertifiedKey* key) { |
| auto database_pb = database_->GetProtobuf(); |
| const int identity = kFirstIdentity; |
| if (database_pb.identities().size() <= identity) { |
| LOG(ERROR) << __func__ << ": Cannot create a certificate request, identity " |
| << identity << " does not exist."; |
| return false; |
| } |
| |
| std::string nonce; |
| if (!crypto_utility_->GetRandom(kNonceSize, &nonce)) { |
| LOG(ERROR) << __func__ << ": GetRandom(nonce) failed."; |
| return false; |
| } |
| std::string key_blob; |
| std::string public_key; |
| std::string public_key_tpm_format; |
| std::string key_info; |
| std::string proof; |
| const auto& identity_data = database_pb.identities().Get(identity); |
| if (!tpm_utility_->CreateCertifiedKey( |
| key_type, key_usage, identity_data.identity_key().identity_key_blob(), |
| nonce, &key_blob, &public_key, &public_key_tpm_format, &key_info, |
| &proof)) { |
| return false; |
| } |
| key->set_key_blob(key_blob); |
| key->set_public_key(public_key); |
| key->set_key_name(key_label); |
| key->set_public_key_tpm_format(public_key_tpm_format); |
| key->set_certified_key_info(key_info); |
| key->set_certified_key_proof(proof); |
| key->set_key_type(key_type); |
| key->set_key_usage(key_usage); |
| return SaveKey(username, key_label, *key); |
| } |
| |
| bool AttestationService::SaveKey(const std::string& username, |
| const std::string& key_label, |
| const CertifiedKey& key) { |
| if (!username.empty()) { |
| std::string key_data; |
| if (!key.SerializeToString(&key_data)) { |
| LOG(ERROR) << __func__ << ": Failed to serialize protobuf."; |
| return false; |
| } |
| if (!key_store_->Write(username, key_label, key_data)) { |
| LOG(ERROR) << __func__ << ": Failed to store certified key for user."; |
| return false; |
| } |
| } else { |
| if (!AddDeviceKey(key_label, key)) { |
| LOG(ERROR) << __func__ << ": Failed to store certified key for device."; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool AttestationService::DeleteKey(const std::string& username, |
| const std::string& key_label) { |
| if (!username.empty()) { |
| return key_store_->Delete(username, key_label); |
| } else { |
| return RemoveDeviceKey(key_label); |
| } |
| } |
| |
| bool AttestationService::DeleteKeysByPrefix(const std::string& username, |
| const std::string& key_prefix) { |
| if (!username.empty()) { |
| return key_store_->DeleteByPrefix(username, key_prefix); |
| } |
| return RemoveDeviceKeysByPrefix(key_prefix); |
| } |
| |
| bool AttestationService::AddDeviceKey(const std::string& key_label, |
| const CertifiedKey& key) { |
| // If a key by this name already exists, reuse the field. |
| auto* database_pb = database_->GetMutableProtobuf(); |
| bool found = false; |
| for (int i = 0; i < database_pb->device_keys_size(); ++i) { |
| if (database_pb->device_keys(i).key_name() == key_label) { |
| found = true; |
| *database_pb->mutable_device_keys(i) = key; |
| break; |
| } |
| } |
| if (!found) |
| *database_pb->add_device_keys() = key; |
| return database_->SaveChanges(); |
| } |
| |
| bool AttestationService::RemoveDeviceKey(const std::string& key_label) { |
| auto* database_pb = database_->GetMutableProtobuf(); |
| bool found = false; |
| for (int i = 0; i < database_pb->device_keys_size(); ++i) { |
| if (database_pb->device_keys(i).key_name() == key_label) { |
| found = true; |
| int last = database_pb->device_keys_size() - 1; |
| if (i < last) { |
| database_pb->mutable_device_keys()->SwapElements(i, last); |
| } |
| database_pb->mutable_device_keys()->RemoveLast(); |
| break; |
| } |
| } |
| if (found) { |
| if (!database_->SaveChanges()) { |
| LOG(WARNING) << __func__ << ": Failed to persist key deletion."; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool AttestationService::RemoveDeviceKeysByPrefix( |
| const std::string& key_prefix) { |
| // Manipulate the device keys protobuf field. Linear time strategy is to swap |
| // all elements we want to keep to the front and then truncate. |
| auto* device_keys = database_->GetMutableProtobuf()->mutable_device_keys(); |
| int next_keep_index = 0; |
| for (int i = 0; i < device_keys->size(); ++i) { |
| if (device_keys->Get(i).key_name().find(key_prefix) != 0) { |
| // Prefix doesn't match -> keep. |
| if (i != next_keep_index) |
| device_keys->SwapElements(next_keep_index, i); |
| ++next_keep_index; |
| } |
| } |
| // If no matching keys, do nothing and return success. |
| if (next_keep_index == device_keys->size()) { |
| return true; |
| } |
| while (next_keep_index < device_keys->size()) { |
| device_keys->RemoveLast(); |
| } |
| return database_->SaveChanges(); |
| } |
| |
| std::string AttestationService::CreatePEMCertificateChain( |
| const CertifiedKey& key) { |
| if (key.certified_key_credential().empty()) { |
| LOG(WARNING) << "Certificate is empty."; |
| return std::string(); |
| } |
| std::string pem = CreatePEMCertificate(key.certified_key_credential()); |
| if (!key.intermediate_ca_cert().empty()) { |
| pem += "\n"; |
| pem += CreatePEMCertificate(key.intermediate_ca_cert()); |
| } |
| for (int i = 0; i < key.additional_intermediate_ca_cert_size(); ++i) { |
| pem += "\n"; |
| pem += CreatePEMCertificate(key.additional_intermediate_ca_cert(i)); |
| } |
| return pem; |
| } |
| |
| std::string AttestationService::CreatePEMCertificate( |
| const std::string& certificate) { |
| const char kBeginCertificate[] = "-----BEGIN CERTIFICATE-----\n"; |
| const char kEndCertificate[] = "-----END CERTIFICATE-----"; |
| |
| std::string pem = kBeginCertificate; |
| pem += brillo::data_encoding::Base64EncodeWrapLines(certificate); |
| pem += kEndCertificate; |
| return pem; |
| } |
| |
| int AttestationService::ChooseTemporalIndex(const std::string& user, |
| const std::string& origin) { |
| std::string user_hash = crypto::SHA256HashString(user); |
| std::string origin_hash = crypto::SHA256HashString(origin); |
| int histogram[kNumTemporalValues] = {}; |
| auto database_pb = database_->GetProtobuf(); |
| for (int i = 0; i < database_pb.temporal_index_record_size(); ++i) { |
| const AttestationDatabase::TemporalIndexRecord& record = |
| database_pb.temporal_index_record(i); |
| // Ignore out-of-range index values. |
| if (record.temporal_index() < 0 || |
| record.temporal_index() >= kNumTemporalValues) |
| continue; |
| if (record.origin_hash() == origin_hash) { |
| if (record.user_hash() == user_hash) { |
| // We've previously chosen this index for this user, reuse it. |
| return record.temporal_index(); |
| } else { |
| // We've previously chosen this index for another user. |
| ++histogram[record.temporal_index()]; |
| } |
| } |
| } |
| int least_used_index = 0; |
| for (int i = 1; i < kNumTemporalValues; ++i) { |
| if (histogram[i] < histogram[least_used_index]) |
| least_used_index = i; |
| } |
| if (histogram[least_used_index] > 0) { |
| LOG(WARNING) << "Unique origin-specific identifiers have been exhausted."; |
| } |
| // Record our choice for later reference. |
| AttestationDatabase::TemporalIndexRecord* new_record = |
| database_pb.add_temporal_index_record(); |
| new_record->set_origin_hash(origin_hash); |
| new_record->set_user_hash(user_hash); |
| new_record->set_temporal_index(least_used_index); |
| if (!database_->SaveChanges()) { |
| LOG(ERROR) << "Failed to save attestation db when choosing temporal index"; |
| // TODO(louiscollard): Check if any further actions are necessary. |
| } |
| return least_used_index; |
| } |
| |
| bool AttestationService::GetSubjectPublicKeyInfo( |
| KeyType key_type, |
| const std::string& public_key, |
| std::string* public_key_info) const { |
| if (key_type == KEY_TYPE_RSA) { |
| return crypto_utility_->GetRSASubjectPublicKeyInfo(public_key, |
| public_key_info); |
| } else if (key_type == KEY_TYPE_ECC) { |
| // Do nothing, since we always store SubjectPublicKeyInfo in |public_key| |
| // field and will pass it this utility |
| *public_key_info = public_key; |
| return true; |
| } else { |
| LOG(ERROR) << __func__ << ": key_type " << key_type << " isn't supported."; |
| return false; |
| } |
| } |
| |
| void AttestationService::PrepareForEnrollment( |
| InitializeCompleteCallback callback) { |
| if (IsPreparedForEnrollment()) { |
| std::move(callback).Run(true); |
| return; |
| } |
| if (!tpm_utility_->IsTpmReady()) { |
| // Try again later. |
| worker_thread_->task_runner()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AttestationService::PrepareForEnrollment, |
| base::Unretained(this), std::move(callback)), |
| base::TimeDelta::FromSeconds(3)); |
| return; |
| } |
| base::TimeTicks start = base::TimeTicks::Now(); |
| LOG(INFO) << "Attestation: Preparing for enrollment..."; |
| |
| if (!tpm_utility_->IsPCR0Valid()) { |
| LOG(ERROR) << __func__ << "Invalid PCR0 value, aborting."; |
| metrics_.ReportAttestationOpsStatus( |
| kAttestationPrepareForEnrollment, |
| AttestationOpsStatus::kInvalidPcr0Value); |
| return; |
| } |
| |
| KeyType key_type = GetEndorsementKeyType(); |
| |
| // Gather information about the endorsement key. |
| std::string ek_public_key; |
| if (!tpm_utility_->GetEndorsementPublicKey(key_type, &ek_public_key)) { |
| LOG(ERROR) << __func__ << ": Failed to get EK public key with key_type " |
| << key_type; |
| std::move(callback).Run(false); |
| return; |
| } |
| LOG(INFO) << "GetEndorsementPublicKey done. (from start: " |
| << (base::TimeTicks::Now() - start).InMilliseconds() << "ms.)"; |
| |
| std::string ek_certificate; |
| if (!tpm_utility_->GetEndorsementCertificate(key_type, &ek_certificate)) { |
| LOG(ERROR) << __func__ << ": Failed to get " << GetKeyTypeName(key_type) |
| << " EK certificate."; |
| std::move(callback).Run(false); |
| return; |
| } |
| LOG(INFO) << "GetEndorsementCertificate done. (from start: " |
| << (base::TimeTicks::Now() - start).InMilliseconds() << "ms.)"; |
| |
| // Create a new AIK and PCR quotes for the first identity with default |
| // identity features. |
| if (CreateIdentity(default_identity_features_) < 0) { |
| std::move(callback).Run(false); |
| return; |
| } |
| LOG(INFO) << "CreateIdentity done. (from start: " |
| << (base::TimeTicks::Now() - start).InMilliseconds() << "ms.)"; |
| |
| // Store all this in the attestation database. |
| auto* database_pb = database_->GetMutableProtobuf(); |
| TPMCredentials* credentials_pb = database_pb->mutable_credentials(); |
| credentials_pb->set_endorsement_key_type(key_type); |
| credentials_pb->set_endorsement_public_key(ek_public_key); |
| credentials_pb->set_endorsement_credential(ek_certificate); |
| |
| // Encrypt the endorsement credential for all the ACAs we know of. |
| EncryptAllEndorsementCredentials(); |
| |
| if (!database_->SaveChanges()) { |
| LOG(ERROR) << "Attestation: Failed to write database."; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // Ignore errors when removing dependency. If failed this time, will be |
| // re-attempted on next boot. |
| tpm_utility_->RemoveOwnerDependency(); |
| |
| base::TimeDelta delta = (base::TimeTicks::Now() - start); |
| LOG(INFO) << "Attestation: Prepared successfully (" << delta.InMilliseconds() |
| << "ms) with " << GetKeyTypeName(key_type) << " EK."; |
| metrics_.ReportAttestationOpsStatus(kAttestationPrepareForEnrollment, |
| AttestationOpsStatus::kSuccess); |
| std::move(callback).Run(true); |
| } |
| |
| int AttestationService::CreateIdentity(int identity_features) { |
| // The identity we're creating will have the next index in identities. |
| auto* database_pb = database_->GetMutableProtobuf(); |
| const int identity = database_pb->identities().size(); |
| KeyType identity_key_type = GetAttestationIdentityKeyType(); |
| LOG(INFO) << "Attestation: Creating identity " << identity << " with " |
| << GetIdentityFeaturesString(identity_features) << " and " |
| << GetKeyTypeName(identity_key_type) << " AIK."; |
| AttestationDatabase::Identity new_identity_pb; |
| |
| new_identity_pb.set_features(identity_features); |
| if (identity_features & IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID) { |
| auto* identity_key = new_identity_pb.mutable_identity_key(); |
| identity_key->set_enrollment_id(database_pb->enrollment_id()); |
| } |
| if (!tpm_utility_->CreateIdentity(identity_key_type, &new_identity_pb)) { |
| LOG(ERROR) << __func__ << " failed to make a new identity."; |
| return -1; |
| } |
| std::string identity_key_blob_for_quote = |
| new_identity_pb.identity_key().identity_key_blob(); |
| |
| // Quote PCRs and store them in the identity. These quotes are intended to |
| // be valid for the lifetime of the identity key so they do not need |
| // external data. This only works when firmware ensures that these PCRs |
| // will not change unless the TPM owner is cleared. |
| auto* pcr_quote_map = new_identity_pb.mutable_pcr_quotes(); |
| |
| for (int pcr = 0; pcr <= kLastPcrToQuote; ++pcr) { |
| std::string quoted_pcr_value; |
| std::string quoted_data; |
| std::string quote; |
| if (tpm_utility_->QuotePCR(pcr, identity_key_blob_for_quote, |
| "ed_pcr_value, "ed_data, "e)) { |
| Quote quote_pb; |
| quote_pb.set_quote(quote); |
| quote_pb.set_quoted_data(quoted_data); |
| quote_pb.set_quoted_pcr_value(quoted_pcr_value); |
| switch (pcr) { |
| case 1: |
| quote_pb.set_pcr_source_hint(hwid_); |
| break; |
| } |
| auto in = pcr_quote_map->insert(QuoteMap::value_type(pcr, quote_pb)); |
| if (!in.second) { |
| LOG(ERROR) << "Attestation: Failed to store PCR" << pcr |
| << " quote for identity " << identity << "."; |
| return -1; |
| } |
| } else { |
| LOG(ERROR) << "Attestation: Failed to generate quote for PCR" << pcr |
| << "."; |
| return -1; |
| } |
| } |
| |
| #if USE_TPM2 |
| |
| // Certify device-specific NV data and insert them in the identity when |
| // we can certify them. This is an almost identical process to the PCR |
| // quotes above. |
| |
| for (int i = 0; i < base::size(kNvramQuoteTypeInIdentityData); ++i) { |
| if (!InsertCertifiedNvramData(kNvramQuoteTypeInIdentityData[i], |
| false /* must_be_present */, |
| &new_identity_pb)) { |
| return -1; |
| } |
| } |
| |
| // Certify the RSA EK cert only when we are using non-RSA EK. In this case, we |
| // don't provide the RSA EK cert which originally is used for calculating the |
| // Enrollment ID. |
| if ((identity_features & IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID) && |
| GetEndorsementKeyType() != KEY_TYPE_RSA) { |
| if (!InsertCertifiedNvramData(RSA_PUB_EK_CERT, true /* must_be_present */, |
| &new_identity_pb)) { |
| return -1; |
| } |
| } |
| |
| #endif |
| database_pb->add_identities()->CopyFrom(new_identity_pb); |
| // Return the index of the newly created identity. |
| return database_pb->identities().size() - 1; |
| } |
| |
| bool AttestationService::QuoteNvramData(NVRAMQuoteType quote_type, |
| const IdentityKey& identity_key, |
| Quote* quote) { |
| #if USE_TPM2 |
| const int id = nvram_quote_type_to_index_data_[quote_type]; |
| const char* quote_name = kNvramIndexData[id].quote_name; |
| const uint32_t nv_index = kNvramIndexData[id].nv_index; |
| uint16_t nv_size = kNvramIndexData[id].nv_size; |
| |
| if (!nv_size) { |
| uint16_t nv_data_size = 0; |
| if (tpm_utility_->GetNVDataSize(nv_index, &nv_data_size)) { |
| nv_size = nv_data_size; |
| } else { |
| LOG(ERROR) << "Attestation: Failed to obtain data about the " |
| << quote_name << "."; |
| } |
| } |
| |
| if (nv_size) { |
| auto identity_key_blob = identity_key.identity_key_blob(); |
| std::string certified_value; |
| std::string signature; |
| |
| if (tpm_utility_->CertifyNV(nv_index, nv_size, identity_key_blob, |
| &certified_value, &signature)) { |
| quote->set_quote(signature); |
| quote->set_quoted_data(certified_value); |
| return true; |
| } else { |
| LOG(WARNING) << "Attestation: Failed to certify " << quote_name |
| << " NV data of size " << nv_size << " at index " << std::hex |
| << std::showbase << nv_index << "."; |
| } |
| } |
| #else |
| LOG(ERROR) << __func__ << ": Should not be called for TPM 1.2 devices."; |
| #endif |
| |
| return false; |
| } |
| |
| bool AttestationService::InsertCertifiedNvramData( |
| NVRAMQuoteType quote_type, |
| bool must_be_present, |
| AttestationDatabase::Identity* identity) { |
| #if USE_TPM2 |
| Quote quote; |
| if (!QuoteNvramData(quote_type, identity->identity_key(), "e)) { |
| return !must_be_present; |
| } |
| |
| auto* nv_quote_map = identity->mutable_nvram_quotes(); |
| auto in_bid = nv_quote_map->insert(QuoteMap::value_type(quote_type, quote)); |
| if (in_bid.second) { |
| return true; |
| } else { |
| LOG(ERROR) << "Attestation: Failed to store " |
| << kNvramIndexData[nvram_quote_type_to_index_data_[quote_type]] |
| .quote_name |
| << " quote for identity " << identity << "."; |
| } |
| #else |
| LOG(ERROR) << __func__ << ": Should not be called for TPM 1.2 devices."; |
| #endif |
| |
| return false; |
| } |
| |
| int AttestationService::GetIdentitiesCount() const { |
| return database_->GetProtobuf().identities().size(); |
| } |
| |
| int AttestationService::GetIdentityFeatures(int identity) const { |
| return database_->GetProtobuf().identities().Get(identity).features(); |
| } |
| |
| AttestationService::IdentityCertificateMap |
| AttestationService::GetIdentityCertificateMap() const { |
| return database_->GetProtobuf().identity_certificates(); |
| } |
| |
| bool AttestationService::EncryptAllEndorsementCredentials() { |
| auto* database_pb = database_->GetMutableProtobuf(); |
| base::Optional<std::string> ek_certificate = GetEndorsementCertificate(); |
| if (!ek_certificate.has_value()) { |
| LOG(ERROR) << "Attestation: Failed to obtain endorsement certificate."; |
| return false; |
| } |
| |
| TPMCredentials* credentials_pb = database_pb->mutable_credentials(); |
| for (int aca = kDefaultACA; aca < kMaxACATypeInternal; ++aca) { |
| if (credentials_pb->encrypted_endorsement_credentials().count(aca)) { |
| continue; |
| } |
| ACAType aca_type = GetACAType(static_cast<ACATypeInternal>(aca)); |
| LOG(INFO) << "Attestation: Encrypting endorsement credential for " |
| << GetACAName(aca_type) << "."; |
| if (!EncryptDataForAttestationCA( |
| aca_type, ek_certificate.value(), |
| &(*credentials_pb |
| ->mutable_encrypted_endorsement_credentials())[aca])) { |
| LOG(ERROR) << "Attestation: Failed to encrypt EK certificate for " |
| << GetACAName(static_cast<ACAType>(aca)) << "."; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool AttestationService::EncryptDataForAttestationCA( |
| ACAType aca_type, const std::string& data, EncryptedData* encrypted_data) { |
| const GoogleRsaPublicKey& key = google_keys_.ca_encryption_key(aca_type); |
| if (!crypto_utility_->EncryptDataForGoogle(data, key.modulus_in_hex(), |
| key.key_id(), encrypted_data)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool AttestationService::ActivateAttestationKeyInternal( |
| int identity, |
| ACAType aca_type, |
| KeyType ek_key_type, |
| const EncryptedIdentityCredential& encrypted_certificate, |
| bool save_certificate, |
| std::string* certificate, |
| int* certificate_index) { |
| const auto& database_pb = database_->GetProtobuf(); |
| if (database_pb.identities().size() <= identity) { |
| LOG(ERROR) << __func__ << ": Enrollment is not possible, identity " |
| << identity << " does not exist."; |
| return false; |
| } |
| const auto& identity_data = database_pb.identities().Get(identity); |
| std::string certificate_local; |
| if (encrypted_certificate.tpm_version() == TPM_1_2) { |
| // TPM 1.2 style activate. |
| if (!tpm_utility_->ActivateIdentity( |
| identity_data.identity_key().identity_key_blob(), |
| encrypted_certificate.asym_ca_contents(), |
| encrypted_certificate.sym_ca_attestation(), &certificate_local)) { |
| LOG(ERROR) << __func__ << ": Failed to activate identity " << identity |
| << "."; |
| return false; |
| } |
| } else { |
| // TPM 2.0 style activate. |
| std::string credential; |
| if (!tpm_utility_->ActivateIdentityForTpm2( |
| ek_key_type, identity_data.identity_key().identity_key_blob(), |
| encrypted_certificate.encrypted_seed(), |
| encrypted_certificate.credential_mac(), |
| encrypted_certificate.wrapped_certificate().wrapped_key(), |
| &credential)) { |
| LOG(ERROR) << __func__ << ": Failed to activate identity " << identity |
| << "."; |
| return false; |
| } |
| if (!crypto_utility_->DecryptIdentityCertificateForTpm2( |
| credential, encrypted_certificate.wrapped_certificate(), |
| &certificate_local)) { |
| LOG(ERROR) << __func__ << ": Failed to decrypt certificate for identity " |
| << identity << "."; |
| return false; |
| } |
| } |
| if (save_certificate) { |
| if (!tpm_utility_->IsPCR0Valid()) { |
| LOG(ERROR) << __func__ << "Invalid PCR0 value, aborting."; |
| metrics_.ReportAttestationOpsStatus( |
| kAttestationActivateAttestationKey, |
| AttestationOpsStatus::kInvalidPcr0Value); |
| return false; |
| } |
| |
| int index; |
| AttestationDatabase_IdentityCertificate* identity_certificate = |
| FindOrCreateIdentityCertificate(identity, aca_type, &index); |
| if (!identity_certificate) { |
| LOG(ERROR) << __func__ << ": Failed to find or create an identity" |
| << " certificate for identity " << identity << " with " |
| << GetACAName(aca_type) << "."; |
| return false; |
| } |
| // Set the credential obtained when activating the identity with the |
| // response. |
| identity_certificate->set_identity_credential(certificate_local); |
| if (!database_->SaveChanges()) { |
| LOG(ERROR) << __func__ << ": Failed to persist database changes."; |
| return false; |
| } |
| if (certificate_index) { |
| *certificate_index = index; |
| } |
| } |
| if (certificate) { |
| *certificate = certificate_local; |
| } |
| return true; |
| } |
| |
| void AttestationService::GetEnrollmentPreparations( |
| const GetEnrollmentPreparationsRequest& request, |
| const GetEnrollmentPreparationsCallback& callback) { |
| auto result = std::make_shared<GetEnrollmentPreparationsReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::GetEnrollmentPreparationsTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<GetEnrollmentPreparationsReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::GetEnrollmentPreparationsTask( |
| const GetEnrollmentPreparationsRequest& request, |
| const std::shared_ptr<GetEnrollmentPreparationsReply>& result) { |
| for (int aca = kDefaultACA; aca < kMaxACATypeInternal; ++aca) { |
| ACAType aca_type = GetACAType(static_cast<ACATypeInternal>(aca)); |
| if (!request.has_aca_type() || aca_type == request.aca_type()) { |
| (*result->mutable_enrollment_preparations())[aca_type] = |
| IsPreparedForEnrollmentWithACA(aca_type); |
| } |
| } |
| } |
| |
| void AttestationService::GetStatus(const GetStatusRequest& request, |
| const GetStatusCallback& callback) { |
| auto result = std::make_shared<GetStatusReply>(); |
| base::Closure task = base::Bind(&AttestationService::GetStatusTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<GetStatusReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| bool AttestationService::IsVerifiedMode() const { |
| if (!tpm_utility_->IsTpmReady()) { |
| VLOG(2) << __func__ << ": Tpm is not ready."; |
| return false; |
| } |
| std::string pcr_value; |
| if (!tpm_utility_->ReadPCR(0, &pcr_value)) { |
| LOG(WARNING) << __func__ << ": Failed to read PCR0."; |
| return false; |
| } |
| return (pcr_value == GetPCRValueForMode(kVerifiedBootMode)); |
| } |
| |
| void AttestationService::GetStatusTask( |
| const GetStatusRequest& request, |
| const std::shared_ptr<GetStatusReply>& result) { |
| result->set_prepared_for_enrollment(IsPreparedForEnrollment()); |
| result->set_enrolled(IsEnrolled()); |
| for (int i = 0, count = GetIdentitiesCount(); i < count; ++i) { |
| GetStatusReply::Identity* identity = result->mutable_identities()->Add(); |
| identity->set_features(GetIdentityFeatures(i)); |
| } |
| AttestationService::IdentityCertificateMap map = GetIdentityCertificateMap(); |
| for (auto it = map.cbegin(), end = map.cend(); it != end; ++it) { |
| GetStatusReply::IdentityCertificate identity_certificate; |
| identity_certificate.set_identity(it->second.identity()); |
| identity_certificate.set_aca(it->second.aca()); |
| result->mutable_identity_certificates()->insert( |
| google::protobuf::Map<int, GetStatusReply::IdentityCertificate>:: |
| value_type(it->first, identity_certificate)); |
| } |
| for (int aca = kDefaultACA; aca < kMaxACATypeInternal; ++aca) { |
| ACAType aca_type = GetACAType(static_cast<ACATypeInternal>(aca)); |
| (*result->mutable_enrollment_preparations())[aca_type] = |
| IsPreparedForEnrollmentWithACA(aca_type); |
| } |
| if (request.extended_status()) { |
| result->set_verified_boot(IsVerifiedMode()); |
| } |
| } |
| |
| void AttestationService::Verify(const VerifyRequest& request, |
| const VerifyCallback& callback) { |
| auto result = std::make_shared<VerifyReply>(); |
| base::Closure task = base::Bind(&AttestationService::VerifyTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<VerifyReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| bool AttestationService::VerifyIdentityBinding(const IdentityBinding& binding) { |
| if (tpm_utility_->GetVersion() == TPM_1_2) { |
| // Reconstruct and hash a serialized TPM_IDENTITY_CONTENTS structure. |
| const std::string header("\x01\x01\x00\x00\x00\x00\x00\x79", 8); |
| std::string digest = base::SHA1HashString(binding.identity_label() + |
| binding.pca_public_key()); |
| std::string identity_public_key_info; |
| if (!GetSubjectPublicKeyInfo(KEY_TYPE_RSA, |
| binding.identity_public_key_der(), |
| &identity_public_key_info)) { |
| LOG(ERROR) << __func__ << ": Failed to get identity public key info."; |
| return false; |
| } |
| if (!crypto_utility_->VerifySignature( |
| crypto_utility_->DefaultDigestAlgoForSignature(), |
| identity_public_key_info, |
| header + digest + binding.identity_public_key_tpm_format(), |
| binding.identity_binding())) { |
| LOG(ERROR) << __func__ |
| << ": Failed to verify identity binding signature."; |
| return false; |
| } |
| } else if (tpm_utility_->GetVersion() == TPM_2_0) { |
| VLOG(1) << __func__ << ": Nothing to do for TPM 2.0."; |
| } else { |
| LOG(ERROR) << __func__ << ": Unsupported TPM version."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool AttestationService::VerifyQuoteSignature( |
| const std::string& aik_public_key_info, |
| const Quote& quote, |
| uint32_t pcr_index) { |
| if (!crypto_utility_->VerifySignature( |
| crypto_utility_->DefaultDigestAlgoForSignature(), aik_public_key_info, |
| quote.quoted_data(), quote.quote())) { |
| LOG(ERROR) << __func__ << ": Signature mismatch."; |
| return false; |
| } |
| if (!tpm_utility_->IsQuoteForPCR(quote.quoted_pcr_value(), |
| quote.quoted_data(), quote.quote(), |
| pcr_index)) { |
| LOG(ERROR) << __func__ << ": Invalid quote."; |
| return false; |
| } |
| return true; |
| } |
| |
| std::string AttestationService::GetPCRValueForMode(const char* mode) const { |
| std::string mode_str(mode, 3); |
| std::string mode_digest = base::SHA1HashString(mode_str); |
| std::string pcr_value; |
| if (tpm_utility_->GetVersion() == TPM_1_2) { |
| // Use SHA-1 digests for TPM 1.2. |
| std::string initial(base::kSHA1Length, 0); |
| pcr_value = base::SHA1HashString(initial + mode_digest); |
| } else if (tpm_utility_->GetVersion() == TPM_2_0) { |
| // Use SHA-256 digests for TPM 2.0. |
| std::string initial(crypto::kSHA256Length, 0); |
| mode_digest.resize(crypto::kSHA256Length); |
| pcr_value = crypto::SHA256HashString(initial + mode_digest); |
| } else { |
| LOG(ERROR) << __func__ << ": Unsupported TPM version."; |
| } |
| return pcr_value; |
| } |
| |
| bool AttestationService::VerifyPCR0Quote(const std::string& aik_public_key_info, |
| const Quote& pcr0_quote) { |
| if (!VerifyQuoteSignature(aik_public_key_info, pcr0_quote, 0)) { |
| return false; |
| } |
| |
| // Check if the PCR0 value represents a known mode. |
| for (size_t i = 0; i < base::size(kKnownBootModes); ++i) { |
| std::string pcr_value = GetPCRValueForMode(kKnownBootModes[i]); |
| if (pcr0_quote.quoted_pcr_value() == pcr_value) { |
| LOG(INFO) << "PCR0: " << GetDescriptionForMode(kKnownBootModes[i]); |
| return true; |
| } |
| } |
| LOG(WARNING) << "PCR0 value not recognized."; |
| return true; |
| } |
| |
| bool AttestationService::VerifyPCR1Quote(const std::string& aik_public_key_info, |
| const Quote& pcr1_quote) { |
| if (!VerifyQuoteSignature(aik_public_key_info, pcr1_quote, 1)) { |
| return false; |
| } |
| |
| // Check that the source hint is correctly populated. |
| if (hwid_ != pcr1_quote.pcr_source_hint()) { |
| LOG(ERROR) << "PCR1 source hint does not match HWID: " << hwid_; |
| return false; |
| } |
| |
| LOG(INFO) << "PCR1 verified as " << hwid_; |
| return true; |
| } |
| |
| bool AttestationService::GetCertifiedKeyDigest( |
| const std::string& public_key_info, |
| const std::string& public_key_tpm_format, |
| std::string* key_digest) { |
| if (tpm_utility_->GetVersion() == TPM_1_2) { |
| return crypto_utility_->GetKeyDigest(public_key_info, key_digest); |
| } else if (tpm_utility_->GetVersion() == TPM_2_0) { |
| // TPM_ALG_SHA256 = 0x000B, here in big-endian order. |
| std::string prefix("\x00\x0B", 2); |
| key_digest->assign(prefix + |
| crypto::SHA256HashString(public_key_tpm_format)); |
| return true; |
| } |
| LOG(ERROR) << __func__ << ": Unsupported TPM version."; |
| return false; |
| } |
| |
| bool AttestationService::VerifyCertifiedKey( |
| const std::string& aik_public_key_info, |
| const std::string& public_key_info, |
| const std::string& public_key_tpm_format, |
| const std::string& key_info, |
| const std::string& proof) { |
| if (!crypto_utility_->VerifySignature( |
| crypto_utility_->DefaultDigestAlgoForSignature(), aik_public_key_info, |
| key_info, proof)) { |
| LOG(ERROR) << __func__ << ": Bad key signature."; |
| return false; |
| } |
| std::string key_digest; |
| if (!GetCertifiedKeyDigest(public_key_info, public_key_tpm_format, |
| &key_digest)) { |
| LOG(ERROR) << __func__ << ": Failed to get key digest."; |
| return false; |
| } |
| if (key_info.find(key_digest) == std::string::npos) { |
| LOG(ERROR) << __func__ << ": Public key mismatch."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool AttestationService::VerifyCertifiedKeyGeneration( |
| const std::string& aik_key_blob, const std::string& aik_public_key_info) { |
| std::string nonce; |
| if (!crypto_utility_->GetRandom(kNonceSize, &nonce)) { |
| LOG(ERROR) << __func__ << ": GetRandom(nonce) failed."; |
| return false; |
| } |
| std::vector<KeyType> key_type_candidates; |
| if (tpm_utility_->GetVersion() == TPM_2_0) { |
| key_type_candidates = {KEY_TYPE_RSA, KEY_TYPE_ECC}; |
| } else { |
| key_type_candidates = {KEY_TYPE_RSA}; |
| } |
| |
| for (KeyType key_type : key_type_candidates) { |
| std::string key_blob; |
| std::string public_key_der; |
| std::string public_key_tpm_format; |
| std::string key_info; |
| std::string proof; |
| if (!tpm_utility_->CreateCertifiedKey( |
| key_type, KEY_USAGE_SIGN, aik_key_blob, nonce, &key_blob, |
| &public_key_der, &public_key_tpm_format, &key_info, &proof)) { |
| LOG(ERROR) << __func__ |
| << ": Failed to create certified key for key_type: " |
| << key_type; |
| return false; |
| } |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo(key_type, public_key_der, &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Failed to get public key info for key_type: " |
| << key_type; |
| return false; |
| } |
| if (!VerifyCertifiedKey(aik_public_key_info, public_key_info, |
| public_key_tpm_format, key_info, proof)) { |
| LOG(ERROR) << __func__ |
| << ": Bad certified key for key_type: " << key_type; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool AttestationService::VerifyActivateIdentity( |
| const std::string& aik_public_key_tpm_format) { |
| std::string rsa_ek_public_key; |
| if (!tpm_utility_->GetEndorsementPublicKey(KEY_TYPE_RSA, |
| &rsa_ek_public_key)) { |
| LOG(ERROR) << __func__ |
| << ": Can't get RSA EK public key for VerifyActivateIdentity."; |
| return false; |
| } |
| std::string test_credential = "test credential"; |
| EncryptedIdentityCredential encrypted_credential; |
| if (!crypto_utility_->EncryptIdentityCredential( |
| tpm_utility_->GetVersion(), test_credential, rsa_ek_public_key, |
| aik_public_key_tpm_format, &encrypted_credential)) { |
| LOG(ERROR) << __func__ << ": Failed to encrypt identity credential"; |
| return false; |
| } |
| if (!ActivateAttestationKeyInternal(kFirstIdentity, DEFAULT_ACA, KEY_TYPE_RSA, |
| encrypted_credential, false, nullptr, |
| nullptr)) { |
| LOG(ERROR) << __func__ << ": Failed to activate identity"; |
| return false; |
| } |
| return true; |
| } |
| |
| void AttestationService::VerifyTask( |
| const VerifyRequest& request, const std::shared_ptr<VerifyReply>& result) { |
| result->set_verified(false); |
| |
| base::Optional<std::string> ek_public_key = GetEndorsementPublicKey(); |
| if (!ek_public_key.has_value()) { |
| LOG(ERROR) << __func__ << ": Endorsement key not available."; |
| return; |
| } |
| |
| base::Optional<std::string> ek_cert = GetEndorsementCertificate(); |
| if (!ek_cert.has_value()) { |
| LOG(ERROR) << __func__ << ": Endorsement cert not available."; |
| return; |
| } |
| |
| std::string issuer; |
| if (!crypto_utility_->GetCertificateIssuerName(ek_cert.value(), &issuer)) { |
| LOG(ERROR) << __func__ << ": Failed to get certificate issuer."; |
| return; |
| } |
| std::string ca_public_key; |
| if (!GetAuthorityPublicKey(issuer, request.cros_core(), &ca_public_key)) { |
| LOG(ERROR) << __func__ << ": Failed to get CA public key."; |
| return; |
| } |
| if (!crypto_utility_->VerifyCertificate(ek_cert.value(), ca_public_key)) { |
| LOG(WARNING) << __func__ << ": Bad endorsement credential."; |
| return; |
| } |
| |
| // Verifies that the given public key matches the one in the credential. |
| |
| // Gets the public key by GetCertificatePublicKey and |
| // GetCertificateSubjectPublicKeyInfo for TPM1.2 and TPM2.0 respectively. |
| // TODO(crbug/942487): Only use GetCertificateSubjectPublicKeyInfo after the |
| // bug is resolved. |
| std::string cert_public_key_info; |
| switch (tpm_utility_->GetVersion()) { |
| case TPM_1_2: |
| if (!crypto_utility_->GetCertificatePublicKey(ek_cert.value(), |
| &cert_public_key_info)) { |
| LOG(ERROR) << __func__ << ": Failed to call GetCertificatePublicKey."; |
| return; |
| } |
| break; |
| case TPM_2_0: |
| if (!crypto_utility_->GetCertificateSubjectPublicKeyInfo( |
| ek_cert.value(), &cert_public_key_info)) { |
| LOG(ERROR) << __func__ |
| << ": Failed to call GetCertificateSubjectPublicKeyInfo."; |
| return; |
| } |
| break; |
| default: |
| NOTREACHED() << "Unexpected TPM version."; |
| } |
| |
| // Note: Do not use any openssl functions that attempt to decode the public |
| // key. These will fail because openssl does not recognize the OAEP key type, |
| // which could happen for some TPM1.2 chips. |
| if (cert_public_key_info != ek_public_key.value()) { |
| LOG(ERROR) << __func__ << ": Bad certificate public key."; |
| return; |
| } |
| |
| // All done if we only needed to verify EK. Otherwise, continue with full |
| // verification. |
| if (request.ek_only()) { |
| result->set_verified(true); |
| return; |
| } |
| |
| auto database_pb = database_->GetProtobuf(); |
| const auto& identity_data = database_pb.identities().Get(kFirstIdentity); |
| std::string identity_public_key_info; |
| if (!GetSubjectPublicKeyInfo( |
| identity_data.identity_key().identity_key_type(), |
| identity_data.identity_key().identity_public_key_der(), |
| &identity_public_key_info)) { |
| LOG(ERROR) << __func__ << ": Failed to get identity public key info."; |
| return; |
| } |
| if (!VerifyIdentityBinding(identity_data.identity_binding())) { |
| LOG(ERROR) << __func__ << ": Bad identity binding."; |
| return; |
| } |
| if (!tpm_utility_->IsPCR0Valid()) { |
| LOG(ERROR) << __func__ << ": Bad PCR0 value."; |
| metrics_.ReportAttestationOpsStatus( |
| kAttestationVerify, AttestationOpsStatus::kInvalidPcr0Value); |
| } |
| if (!VerifyPCR0Quote(identity_public_key_info, |
| identity_data.pcr_quotes().at(0))) { |
| LOG(ERROR) << __func__ << ": Bad PCR0 quote."; |
| return; |
| } |
| if (!VerifyPCR1Quote(identity_public_key_info, |
| identity_data.pcr_quotes().at(1))) { |
| // Don't fail because many devices don't use PCR1. |
| LOG(WARNING) << __func__ << ": Bad PCR1 quote."; |
| } |
| if (!VerifyCertifiedKeyGeneration( |
| identity_data.identity_key().identity_key_blob(), |
| identity_public_key_info)) { |
| LOG(ERROR) << __func__ << ": Failed to verify certified key generation."; |
| return; |
| } |
| |
| // Originally, we use VerifyActivateIdentity to test ActivateIdentity TPM |
| // command works in TPM 1.2, but we don't really need it for TPM 2.0. Because |
| // we have more complete tests in Cr50, we are planning to obsolete the tests |
| // in the service daemon. Moreover, supporting this would require simulating |
| // the entire attestation flow but pointless. We decide to not to support this |
| // test for ECC EK. |
| if (!VerifyActivateIdentity( |
| identity_data.identity_binding().identity_public_key_tpm_format())) { |
| LOG(ERROR) << __func__ << ": Failed to verify identity activation."; |
| return; |
| } |
| LOG(INFO) << "Attestation: Verified OK."; |
| result->set_verified(true); |
| } |
| |
| void AttestationService::CreateEnrollRequest( |
| const CreateEnrollRequestRequest& request, |
| const CreateEnrollRequestCallback& callback) { |
| auto result = std::make_shared<CreateEnrollRequestReply>(); |
| base::Closure task = base::Bind( |
| &AttestationService::CreateEnrollRequestTask<CreateEnrollRequestRequest>, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<CreateEnrollRequestReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| template <typename RequestType> |
| void AttestationService::CreateEnrollRequestTask( |
| const RequestType& request, |
| const std::shared_ptr<CreateEnrollRequestReply>& result) { |
| if (!CreateEnrollRequestInternal(request.aca_type(), |
| result->mutable_pca_request())) { |
| result->clear_pca_request(); |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| } |
| } |
| |
| void AttestationService::FinishEnroll(const FinishEnrollRequest& request, |
| const FinishEnrollCallback& callback) { |
| auto result = std::make_shared<FinishEnrollReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::FinishEnrollTask<FinishEnrollReply>, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<FinishEnrollReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| template <typename ReplyType> |
| void AttestationService::FinishEnrollTask( |
| const FinishEnrollRequest& request, |
| const std::shared_ptr<ReplyType>& result) { |
| std::string server_error; |
| if (!FinishEnrollInternal(request.aca_type(), request.pca_response(), |
| &server_error)) { |
| if (server_error.empty()) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| } else { |
| result->set_status(STATUS_REQUEST_DENIED_BY_CA); |
| } |
| } |
| } |
| |
| void AttestationService::PostStartEnrollTask( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| base::Closure task = base::Bind(&AttestationService::StartEnrollTask, |
| base::Unretained(this), data); |
| base::Closure reply = |
| base::Bind(&AttestationService::OnEnrollAction, GetWeakPtr(), data); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::Enroll(const EnrollRequest& request, |
| const EnrollCallback& callback) { |
| PostStartEnrollTask(std::make_shared<AttestationFlowData>(request, callback)); |
| } |
| |
| void AttestationService::SendEnrollRequest( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| auto pca_request = ToPcaAgentEnrollRequest(*data); |
| auto on_success = base::Bind(&AttestationService::HandlePcaAgentEnrollReply, |
| GetWeakPtr(), data); |
| auto on_error = |
| base::Bind(&AttestationService::HandlePcaAgentEnrollRequestError, |
| GetWeakPtr(), data); |
| pca_agent_proxy_->EnrollAsync(pca_request, on_success, on_error); |
| } |
| |
| void AttestationService::HandlePcaAgentEnrollRequestError( |
| const std::shared_ptr<AttestationFlowData>& data, brillo::Error*) { |
| LOG(ERROR) << __func__ << ": Error sending enroll request to |pca_agent|"; |
| data->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| enrollment_statuses_[data->aca_type()] = EnrollmentStatus::kNotEnrolled; |
| data->set_action(AttestationFlowAction::kAbort); |
| OnEnrollAction(data); |
| } |
| |
| void AttestationService::OnEnrollAction( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| VLOG(1) << __func__ << ": action is : " << static_cast<int>(data->action()); |
| switch (data->action()) { |
| default: |
| case AttestationFlowAction::kUnknown: |
| LOG(DFATAL) << "Unexpected action code: " |
| << static_cast<int>(data->action()); |
| data->set_status(STATUS_NOT_SUPPORTED); |
| data->ReturnStatus(); |
| return; |
| case AttestationFlowAction::kAbort: |
| data->ReturnStatus(); |
| for (const auto& alias : enrollment_queue_.PopAll(data->aca_type())) { |
| alias->set_status(data->status()); |
| alias->ReturnStatus(); |
| } |
| return; |
| case AttestationFlowAction::kProcessRequest: |
| SendEnrollRequest(data); |
| return; |
| case AttestationFlowAction::kEnqueue: |
| // If in the dbus calling thread the status has changed, re-posts the |
| // task. |
| if (enrollment_statuses_[data->aca_type()] != |
| EnrollmentStatus::kInProgress) { |
| PostStartEnrollTask(data); |
| } else { |
| if (!enrollment_queue_.Push(data)) { |
| data->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| data->ReturnStatus(); |
| } |
| } |
| return; |
| case AttestationFlowAction::kNoop: |
| PostStartCertificateTaskOrReturn(data); |
| for (const auto& alias : enrollment_queue_.PopAll(data->aca_type())) { |
| PostStartCertificateTaskOrReturn(alias); |
| } |
| return; |
| } |
| } |
| |
| void AttestationService::OnGetCertificateAction( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| VLOG(1) << __func__ << ": action is : " << static_cast<int>(data->action()); |
| switch (data->action()) { |
| default: |
| case AttestationFlowAction::kUnknown: |
| LOG(DFATAL) << "Unexpected action code: " |
| << static_cast<int>(data->action()); |
| data->set_status(STATUS_NOT_SUPPORTED); |
| data->ReturnStatus(); |
| return; |
| case AttestationFlowAction::kProcessRequest: |
| SendGetCertificateRequest(data); |
| return; |
| case AttestationFlowAction::kEnqueue: |
| // If no alias found, re-posts the task. Sadly the task will check the key |
| // store again. The good news is, this case should be rare in practice. |
| if (!certificate_queue_.HasAnyAlias(data)) { |
| // Despite of the callee name, it always posts the task here since we |
| // shall get certificate. |
| PostStartCertificateTaskOrReturn(data); |
| } else { |
| // TODO(b/149723745): UMA for respective failure cases. |
| CertificateQueue::PushResult result = certificate_queue_.Push(data); |
| if (result != CertificateQueue::PushResult::kSuccess) { |
| data->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| data->ReturnStatus(); |
| } |
| } |
| return; |
| case AttestationFlowAction::kAbort: |
| case AttestationFlowAction::kNoop: |
| ReturnForAllCertificateRequestAliases(data); |
| return; |
| } |
| } |
| |
| void AttestationService::ReturnForAllCertificateRequestAliases( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| if (data->status() == STATUS_SUCCESS) { |
| data->ReturnCertificate(); |
| std::string certificate = data->certificate(); |
| for (const auto& alias : certificate_queue_.PopAllAliases(data)) { |
| if (data != alias) { |
| alias->set_certificate(data->certificate()); |
| alias->ReturnCertificate(); |
| } |
| } |
| } else { |
| data->ReturnStatus(); |
| for (const auto& alias : certificate_queue_.PopAllAliases(data)) { |
| if (data != alias) { |
| alias->set_status(data->status()); |
| alias->ReturnStatus(); |
| } |
| } |
| } |
| } |
| |
| void AttestationService::StartEnrollTask( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| // When the enrollment is in progress, all the attestation flow entries listen |
| // to the same response. |
| if (enrollment_statuses_[data->aca_type()] == EnrollmentStatus::kInProgress) { |
| data->set_action(AttestationFlowAction::kEnqueue); |
| return; |
| } |
| // This is the place we initialize the enrollment statuses; at this moment |
| // there is no write operation in other threads, so we don't bother using |
| // compare_and_exchange atomic operation. |
| if (enrollment_statuses_[data->aca_type()] == EnrollmentStatus::kUnknown) { |
| enrollment_statuses_[data->aca_type()] = |
| IsEnrolledWithACA(data->aca_type()) ? EnrollmentStatus::kEnrolled |
| : EnrollmentStatus::kNotEnrolled; |
| } |
| // At this point, possible statuses are : kEnrolled and kNotEnrolled. |
| const bool is_enrolled = |
| enrollment_statuses_[data->aca_type()] == EnrollmentStatus::kEnrolled; |
| if (is_enrolled && !data->forced_enrollment()) { |
| data->set_action(AttestationFlowAction::kNoop); |
| return; |
| } |
| if (!is_enrolled && !data->shall_enroll()) { |
| data->set_action(AttestationFlowAction::kAbort); |
| data->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| std::string result_request; |
| if (!CreateEnrollRequestInternal(data->aca_type(), &result_request)) { |
| data->set_action(AttestationFlowAction::kAbort); |
| data->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| enrollment_statuses_[data->aca_type()] = EnrollmentStatus::kInProgress; |
| data->emplace_result_request(std::move(result_request)); |
| data->set_action(AttestationFlowAction::kProcessRequest); |
| } |
| |
| void AttestationService::FinishEnrollTaskV2( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| std::string server_error; |
| if (!FinishEnrollInternal(data->aca_type(), data->result_response(), |
| &server_error)) { |
| if (server_error.empty()) { |
| data->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| } else { |
| data->set_status(STATUS_REQUEST_DENIED_BY_CA); |
| } |
| data->set_action(AttestationFlowAction::kAbort); |
| enrollment_statuses_[data->aca_type()] = EnrollmentStatus::kNotEnrolled; |
| } else { |
| data->set_action(AttestationFlowAction::kNoop); |
| enrollment_statuses_[data->aca_type()] = EnrollmentStatus::kEnrolled; |
| } |
| } |
| |
| void AttestationService::PostStartCertificateTaskOrReturn( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| if (!data->shall_get_certificate()) { |
| data->ReturnStatus(); |
| return; |
| } |
| base::Closure task = base::Bind(&AttestationService::StartCertificateTask, |
| base::Unretained(this), data); |
| base::Closure reply = base::Bind(&AttestationService::OnGetCertificateAction, |
| GetWeakPtr(), data); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::StartCertificateTask( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| DCHECK(data->shall_get_certificate()); |
| |
| if (certificate_queue_.HasAnyAlias(data)) { |
| data->set_action(AttestationFlowAction::kEnqueue); |
| return; |
| } |
| |
| CertifiedKey key; |
| if (!data->forced_get_certificate() && |
| FindKeyByLabel(data->username(), data->key_label(), &key)) { |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo(key.key_type(), key.public_key(), |
| &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Failed to call `GetSubjectPublicKeyInfo()`."; |
| data->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| data->set_action(AttestationFlowAction::kAbort); |
| return; |
| } |
| data->set_public_key(std::move(public_key_info)); |
| data->set_certificate(CreatePEMCertificateChain(key)); |
| data->set_action(AttestationFlowAction::kNoop); |
| return; |
| } |
| // Indirects to the existing logic; by doing this we don't have to worry about |
| // changing the current behavior. |
| const GetCertificateRequest& request = data->get_certificate_request(); |
| auto reply = std::make_shared<CreateCertificateRequestReply>(); |
| CreateCertificateRequestTask(request, reply); |
| if (reply->status() != STATUS_SUCCESS) { |
| data->set_status(reply->status()); |
| data->set_action(AttestationFlowAction::kAbort); |
| return; |
| } |
| data->emplace_result_request(std::move(*(reply->mutable_pca_request()))); |
| data->set_action(AttestationFlowAction::kProcessRequest); |
| // Different from the way we handle enrollment, enqueues |data| right here so |
| // we can check the existence of aliases to tell if the certification in |
| // progress. |
| CertificateQueue::PushResult push_result = certificate_queue_.Push(data); |
| // the certificate queue is in a broken state since we already ensure |
| // |HasAnyAlias| to be |false|; crashes |attestationd| to bring it back to |
| // initial state in any case. |
| CHECK_EQ(push_result, CertificateQueue::PushResult::kSuccess) |
| << "Unexpected error during pushing the first alias to certificate " |
| "queue."; |
| } |
| |
| void AttestationService::FinishCertificateTask( |
| const std::shared_ptr<AttestationFlowData>& data) { |
| // Indirects to the existing logic; by doing this we don't have to worry about |
| // changing the current behavior. |
| FinishCertificateRequestRequest request; |
| request.set_pca_response(data->result_response()); |
| request.set_username(data->username()); |
| request.set_key_label(data->key_label()); |
| auto reply = std::make_shared<FinishCertificateRequestReply>(); |
| FinishCertificateRequestTask(request, reply); |
| if (reply->status() != STATUS_SUCCESS) { |
| data->set_status(reply->status()); |
| data->set_action(AttestationFlowAction::kAbort); |
| return; |
| } |
| data->set_public_key(std::move(*(reply->mutable_public_key()))); |
| data->set_certificate(std::move(*(reply->mutable_certificate()))); |
| data->set_action(AttestationFlowAction::kNoop); |
| } |
| |
| void AttestationService::HandlePcaAgentEnrollReply( |
| const std::shared_ptr<AttestationFlowData>& data, |
| const pca_agent::EnrollReply& pca_reply) { |
| if (pca_reply.status() != STATUS_SUCCESS) { |
| enrollment_statuses_[data->aca_type()] = EnrollmentStatus::kNotEnrolled; |
| data->set_status(pca_reply.status()); |
| data->set_action(AttestationFlowAction::kAbort); |
| OnEnrollAction(data); |
| return; |
| } |
| data->set_result_response(pca_reply.response()); |
| base::Closure task = base::Bind(&AttestationService::FinishEnrollTaskV2, |
| base::Unretained(this), data); |
| base::Closure reply = |
| base::Bind(&AttestationService::OnEnrollAction, GetWeakPtr(), data); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::CreateCertificateRequest( |
| const CreateCertificateRequestRequest& request, |
| const CreateCertificateRequestCallback& callback) { |
| auto result = std::make_shared<CreateCertificateRequestReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::CreateCertificateRequestTask< |
| CreateCertificateRequestRequest>, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<CreateCertificateRequestReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| template <typename RequestType> |
| void AttestationService::CreateCertificateRequestTask( |
| const RequestType& request, |
| const std::shared_ptr<CreateCertificateRequestReply>& result) { |
| const int identity = kFirstIdentity; |
| au
|