Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 5 | #include <array> |
| 6 | |
Qijiang Fan | 713061e | 2021-03-08 15:45:12 +0900 | [diff] [blame] | 7 | #include <base/check.h> |
Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 8 | #include <base/logging.h> |
| 9 | #include <base/strings/string_number_conversions.h> |
| 10 | #include <base/strings/string_util.h> |
| 11 | #include <base/sys_byteorder.h> |
| 12 | |
| 13 | #include "u2fd/tpm_vendor_cmd.h" |
| 14 | |
| 15 | namespace { |
| 16 | |
| 17 | // All TPM extension commands use this struct for input and output. Any other |
| 18 | // data follows immediately after. All values are big-endian over the wire. |
| 19 | struct TpmCmdHeader { |
| 20 | uint16_t tag; // TPM_ST_NO_SESSIONS |
| 21 | uint32_t size; // including this header |
| 22 | uint32_t code; // Command out, Response back. |
| 23 | uint16_t subcommand_code; // Additional command/response codes |
| 24 | } __attribute__((packed)); |
| 25 | |
| 26 | // TPMv2 Spec mandates that vendor-specific command codes have bit 29 set, |
| 27 | // while bits 15-0 indicate the command. All other bits should be zero. We |
| 28 | // define one of those 16-bit command values for Cr50 purposes, and use the |
| 29 | // subcommand_code in struct TpmCmdHeader to further distinguish the desired |
| 30 | // operation. |
| 31 | const uint32_t kTpmCcVendorBit = 0x20000000; |
| 32 | |
| 33 | // Vendor-specific command codes |
| 34 | const uint32_t kTpmCcVendorCr50 = 0x0000; |
| 35 | |
| 36 | // Cr50 vendor-specific subcommand codes. 16 bits available. |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 37 | // TODO(louiscollard): Replace this with constants from cr50 header. |
Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 38 | const uint16_t kVendorCcU2fApdu = 27; |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 39 | const uint16_t kVendorCcU2fGenerate = 44; |
| 40 | const uint16_t kVendorCcU2fSign = 45; |
| 41 | const uint16_t kVendorCcU2fAttest = 46; |
Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 42 | |
| 43 | } // namespace |
| 44 | |
| 45 | namespace u2f { |
| 46 | |
Louis Collard | adf2abd | 2020-06-18 17:33:23 +0800 | [diff] [blame] | 47 | TpmVendorCommandProxy::TpmVendorCommandProxy() {} |
Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 48 | TpmVendorCommandProxy::~TpmVendorCommandProxy() {} |
| 49 | |
Yicheng Li | c03ac7e | 2019-12-09 16:13:26 -0800 | [diff] [blame] | 50 | base::Lock& TpmVendorCommandProxy::GetLock() { |
| 51 | return lock_; |
| 52 | } |
| 53 | |
Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 54 | uint32_t TpmVendorCommandProxy::VendorCommand(uint16_t cc, |
| 55 | const std::string& input, |
| 56 | std::string* output) { |
| 57 | // Pack up the header and the input |
| 58 | TpmCmdHeader header; |
| 59 | header.tag = base::HostToNet16(trunks::TPM_ST_NO_SESSIONS); |
| 60 | header.size = base::HostToNet32(sizeof(header) + input.size()); |
| 61 | header.code = base::HostToNet32(kTpmCcVendorBit | kTpmCcVendorCr50); |
| 62 | header.subcommand_code = base::HostToNet16(cc); |
| 63 | |
| 64 | std::string command(reinterpret_cast<char*>(&header), sizeof(header)); |
| 65 | command += input; |
| 66 | |
| 67 | // Send the command, get the response |
| 68 | VLOG(2) << "Out(" << command.size() |
| 69 | << "): " << base::HexEncode(command.data(), command.size()); |
| 70 | std::string response = SendCommandAndWait(command); |
| 71 | VLOG(2) << "In(" << response.size() |
| 72 | << "): " << base::HexEncode(response.data(), response.size()); |
| 73 | |
| 74 | if (response.size() < sizeof(header)) { |
| 75 | LOG(ERROR) << "TPM response was too short!"; |
| 76 | return -1; |
| 77 | } |
| 78 | |
| 79 | // Unpack the response header and any output |
| 80 | memcpy(&header, response.data(), sizeof(header)); |
| 81 | header.size = base::NetToHost32(header.size); |
| 82 | header.code = base::NetToHost32(header.code); |
| 83 | |
| 84 | // Error of some sort? |
| 85 | if (header.code) { |
| 86 | if ((header.code & kVendorRcErr) == kVendorRcErr) { |
| 87 | LOG(WARNING) << "TPM error code 0x" << std::hex << header.code; |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | // Pass back any reply beyond the header |
| 92 | *output = response.substr(sizeof(header)); |
| 93 | |
| 94 | return header.code; |
| 95 | } |
| 96 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 97 | namespace { |
| 98 | |
| 99 | template <typename Request> |
| 100 | std::string RequestToString(const Request& req) { |
| 101 | return std::string(reinterpret_cast<const char*>(&req), sizeof(req)); |
Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 102 | } |
| 103 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 104 | template <> |
Louis Collard | be15928 | 2020-02-12 15:29:56 +0800 | [diff] [blame] | 105 | std::string RequestToString(const struct u2f_attest_req& req) { |
Andrey Pronin | b708464 | 2019-12-30 17:32:38 -0800 | [diff] [blame] | 106 | return std::string(reinterpret_cast<const char*>(&req), |
Louis Collard | be15928 | 2020-02-12 15:29:56 +0800 | [diff] [blame] | 107 | offsetof(struct u2f_attest_req, data) + req.dataLen); |
Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 108 | } |
| 109 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 110 | } // namespace |
Vincent Palatin | 7b89956 | 2017-09-27 11:07:14 +0200 | [diff] [blame] | 111 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 112 | template <typename Request, typename Response> |
| 113 | uint32_t TpmVendorCommandProxy::VendorCommandStruct(uint16_t cc, |
| 114 | const Request& input, |
| 115 | Response* output) { |
| 116 | std::string output_str; |
| 117 | uint32_t resp_code = VendorCommand(cc, RequestToString(input), &output_str); |
Vincent Palatin | 7b89956 | 2017-09-27 11:07:14 +0200 | [diff] [blame] | 118 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 119 | if (resp_code == 0) { |
| 120 | if (output_str.size() == sizeof(*output)) { |
| 121 | memcpy(output, output_str.data(), sizeof(*output)); |
| 122 | } else { |
| 123 | LOG(ERROR) << "Invalid response size for successful vendor command, " |
| 124 | << "expected: " << sizeof(*output) |
| 125 | << ", actual: " << output_str.size(); |
| 126 | return kVendorRcInvalidResponse; |
| 127 | } |
Vincent Palatin | 7b89956 | 2017-09-27 11:07:14 +0200 | [diff] [blame] | 128 | } |
| 129 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 130 | return resp_code; |
Vincent Palatin | 7b89956 | 2017-09-27 11:07:14 +0200 | [diff] [blame] | 131 | } |
| 132 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 133 | uint32_t TpmVendorCommandProxy::SendU2fApdu(const std::string& req, |
| 134 | std::string* resp_out) { |
| 135 | return VendorCommand(kVendorCcU2fApdu, req, resp_out); |
| 136 | } |
| 137 | |
Louis Collard | be15928 | 2020-02-12 15:29:56 +0800 | [diff] [blame] | 138 | uint32_t TpmVendorCommandProxy::SendU2fGenerate( |
Yicheng Li | 519a56d | 2020-09-17 19:16:31 +0000 | [diff] [blame] | 139 | const struct u2f_generate_req& req, struct u2f_generate_resp* resp_out) { |
Yicheng Li | 5ca11f5 | 2020-05-14 16:49:48 -0700 | [diff] [blame] | 140 | if ((req.flags & U2F_UV_ENABLED_KH) != 0) { |
| 141 | LOG(ERROR) << "Invalid flags in u2f_generate request."; |
| 142 | return -1; |
| 143 | } |
| 144 | |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 145 | return VendorCommandStruct(kVendorCcU2fGenerate, req, resp_out); |
| 146 | } |
| 147 | |
Yicheng Li | 5ca11f5 | 2020-05-14 16:49:48 -0700 | [diff] [blame] | 148 | uint32_t TpmVendorCommandProxy::SendU2fGenerate( |
| 149 | const struct u2f_generate_req& req, |
| 150 | struct u2f_generate_versioned_resp* resp_out) { |
| 151 | if ((req.flags & U2F_UV_ENABLED_KH) == 0) { |
| 152 | LOG(ERROR) << "Invalid flags in u2f_generate request."; |
| 153 | return -1; |
| 154 | } |
| 155 | |
| 156 | return VendorCommandStruct(kVendorCcU2fGenerate, req, resp_out); |
| 157 | } |
| 158 | |
| 159 | template <typename Request> |
| 160 | uint32_t TpmVendorCommandProxy::SendU2fSignGeneric( |
| 161 | const Request& req, struct u2f_sign_resp* resp_out) { |
Louis Collard | 6942935 | 2019-03-07 10:57:09 +0800 | [diff] [blame] | 162 | std::string output_str; |
| 163 | uint32_t resp_code = |
| 164 | VendorCommand(kVendorCcU2fSign, RequestToString(req), &output_str); |
| 165 | |
| 166 | if (resp_code == 0) { |
| 167 | // A success response may or may not have a body, depending on whether the |
| 168 | // request was a full sign request, or simply a 'check only' request, to |
| 169 | // test ownership of the specified key handle. |
Yicheng Li | 5ca11f5 | 2020-05-14 16:49:48 -0700 | [diff] [blame] | 170 | if (((req.flags & U2F_AUTH_CHECK_ONLY) == U2F_AUTH_CHECK_ONLY) && |
| 171 | output_str.size() == 0) { |
Louis Collard | 6942935 | 2019-03-07 10:57:09 +0800 | [diff] [blame] | 172 | // We asked to test ownership of a key handle; success response code |
| 173 | // indicates it is owned. No response body expected. |
| 174 | return resp_code; |
Louis Collard | be15928 | 2020-02-12 15:29:56 +0800 | [diff] [blame] | 175 | } else if (output_str.size() == sizeof(struct u2f_sign_resp)) { |
Louis Collard | 6942935 | 2019-03-07 10:57:09 +0800 | [diff] [blame] | 176 | DCHECK(resp_out); // It is a programming error for this to fail. |
| 177 | memcpy(resp_out, output_str.data(), sizeof(*resp_out)); |
| 178 | } else { |
| 179 | LOG(ERROR) << "Invalid response size for successful vendor command, " |
Louis Collard | be15928 | 2020-02-12 15:29:56 +0800 | [diff] [blame] | 180 | << "expected: " |
| 181 | << (resp_out ? sizeof(struct u2f_sign_resp) : 0) |
Louis Collard | 6942935 | 2019-03-07 10:57:09 +0800 | [diff] [blame] | 182 | << ", actual: " << output_str.size(); |
| 183 | return kVendorRcInvalidResponse; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | return resp_code; |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 188 | } |
| 189 | |
Yicheng Li | 5ca11f5 | 2020-05-14 16:49:48 -0700 | [diff] [blame] | 190 | uint32_t TpmVendorCommandProxy::SendU2fSign(const struct u2f_sign_req& req, |
| 191 | u2f_sign_resp* resp) { |
| 192 | return SendU2fSignGeneric(req, resp); |
| 193 | } |
| 194 | |
| 195 | uint32_t TpmVendorCommandProxy::SendU2fSign( |
| 196 | const struct u2f_sign_versioned_req& req, u2f_sign_resp* resp) { |
| 197 | return SendU2fSignGeneric(req, resp); |
| 198 | } |
| 199 | |
Louis Collard | be15928 | 2020-02-12 15:29:56 +0800 | [diff] [blame] | 200 | uint32_t TpmVendorCommandProxy::SendU2fAttest( |
| 201 | const struct u2f_attest_req& req, struct u2f_attest_resp* resp_out) { |
Louis Collard | 3925fc9 | 2019-02-26 18:43:22 +0800 | [diff] [blame] | 202 | return VendorCommandStruct(kVendorCcU2fAttest, req, resp_out); |
| 203 | } |
| 204 | |
| 205 | uint32_t TpmVendorCommandProxy::GetG2fCertificate(std::string* cert_out) { |
| 206 | constexpr std::array<uint8_t, 0x23> kCertRequest{ |
| 207 | 0x80, 0x02, // TPM_ST_SESSIONS |
| 208 | 0x00, 0x00, 0x00, 0x23, // size |
| 209 | 0x00, 0x00, 0x01, 0x4e, // TPM_CC_NV_READ |
| 210 | 0x01, 0x3f, 0xff, 0x02, // authHandle : TPMI_RH_NV_AUTH |
| 211 | 0x01, 0x3f, 0xff, 0x02, // nvIndex : TPMI_RH_NV_INDEX |
| 212 | 0x00, 0x00, 0x00, 0x09, // authorizationSize : UINT32 |
| 213 | 0x40, 0x00, 0x00, 0x09, // sessionHandle : empty password |
| 214 | 0x00, 0x00, 0x00, 0x00, 0x00, // nonce, sessionAttributes, hmac |
| 215 | 0x01, 0x3b, // nvSize : UINT16 |
| 216 | 0x00, 0x00 // nvOffset : UINT16 |
| 217 | }; |
| 218 | |
| 219 | constexpr std::array<uint8_t, 16> kExpectedCertResponseHeader{ |
| 220 | 0x80, 0x02, // TPM_ST_SESSIONS |
| 221 | 0x00, 0x00, 0x01, 0x50, // responseSize |
| 222 | 0x00, 0x00, 0x00, 0x00, // responseCode : TPM_RC_SUCCESS |
| 223 | 0x00, 0x00, 0x01, 0x3d, // parameterSize |
| 224 | 0x01, 0x3b, // TPM2B_MAX_NV_BUFFER : size |
| 225 | }; |
| 226 | |
| 227 | constexpr int kCertSize = 0x013b; |
| 228 | constexpr int kTpmResponseHeaderSize = 10; |
| 229 | constexpr int kExpectedCertResponseSize = 0x0150; |
| 230 | |
| 231 | std::string req(kCertRequest.begin(), kCertRequest.end()); |
| 232 | |
| 233 | VLOG(2) << "Out(" << req.size() |
| 234 | << "): " << base::HexEncode(req.data(), req.size()); |
| 235 | |
| 236 | std::string resp = SendCommandAndWait(req); |
| 237 | |
| 238 | VLOG(2) << "In(" << resp.size() |
| 239 | << "): " << base::HexEncode(resp.data(), resp.size()); |
| 240 | |
| 241 | if (resp.size() < kTpmResponseHeaderSize) { |
| 242 | return kVendorRcInvalidResponse; |
| 243 | } |
| 244 | |
| 245 | // TODO(louiscollard): This, in a less horrible way. |
| 246 | if (resp.size() != kExpectedCertResponseSize || |
| 247 | resp.compare(0, 16, |
| 248 | std::string(kExpectedCertResponseHeader.begin(), |
| 249 | kExpectedCertResponseHeader.end())) != 0) { |
| 250 | return base::NetToHost32( |
| 251 | *reinterpret_cast<const uint32_t*>(&resp.data()[6])); |
| 252 | } |
| 253 | |
| 254 | *cert_out = resp.substr(kExpectedCertResponseHeader.size(), kCertSize); |
| 255 | |
| 256 | return 0; |
| 257 | } |
| 258 | |
| 259 | void TpmVendorCommandProxy::LogIndividualCertificate() { |
| 260 | std::string cert; |
| 261 | |
| 262 | uint32_t cert_status = GetG2fCertificate(&cert); |
| 263 | |
| 264 | if (cert_status) { |
| 265 | VLOG(1) << "Failed to retrieve G2F certificate: " << std::hex |
| 266 | << cert_status; |
| 267 | } else { |
| 268 | VLOG(1) << "Certificate: " << base::HexEncode(cert.data(), cert.size()); |
| 269 | } |
| 270 | } |
| 271 | |
Vincent Palatin | c6c7e4e | 2017-06-15 15:45:05 +0200 | [diff] [blame] | 272 | } // namespace u2f |