forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathattested_credential_data.cc
240 lines (198 loc) · 8.42 KB
/
attested_credential_data.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright 2017 The Chromium 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 "device/fido/attested_credential_data.h"
#include <algorithm>
#include <utility>
#include "base/numerics/safe_math.h"
#include "components/cbor/reader.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/cbor_extract.h"
#include "device/fido/ed25519_public_key.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/p256_public_key.h"
#include "device/fido/public_key.h"
#include "device/fido/rsa_public_key.h"
using device::cbor_extract::IntKey;
using device::cbor_extract::Is;
using device::cbor_extract::StepOrByte;
using device::cbor_extract::Stop;
namespace device {
// static
base::Optional<std::pair<AttestedCredentialData, base::span<const uint8_t>>>
AttestedCredentialData::ConsumeFromCtapResponse(
base::span<const uint8_t> buffer) {
if (buffer.size() < kAaguidLength)
return base::nullopt;
auto aaguid = buffer.first<kAaguidLength>();
buffer = buffer.subspan(kAaguidLength);
if (buffer.size() < kCredentialIdLengthLength)
return base::nullopt;
auto credential_id_length_span = buffer.first<kCredentialIdLengthLength>();
const size_t credential_id_length =
(base::strict_cast<size_t>(credential_id_length_span[0]) << 8) |
base::strict_cast<size_t>(credential_id_length_span[1]);
buffer = buffer.subspan(kCredentialIdLengthLength);
if (buffer.size() < credential_id_length)
return base::nullopt;
auto credential_id = buffer.first(credential_id_length);
buffer = buffer.subspan(credential_id_length);
size_t public_key_byte_len;
base::Optional<cbor::Value> public_key_cbor =
cbor::Reader::Read(buffer, &public_key_byte_len);
if (!public_key_cbor || !public_key_cbor->is_map()) {
FIDO_LOG(ERROR) << "CBOR error in COSE public key";
return base::nullopt;
}
const base::span<const uint8_t> public_key_cbor_bytes(
buffer.first(public_key_byte_len));
buffer = buffer.subspan(public_key_byte_len);
const cbor::Value::MapValue& public_key_map = public_key_cbor->GetMap();
struct COSEKey {
const int64_t* alg;
const int64_t* kty;
} cose_key;
static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = {
// clang-format off
// kAlg is required to be present. See
// https://www.w3.org/TR/webauthn/#credentialpublickey
// COSE allows it to be a string or an integer. However, WebAuthn defines
// COSEAlgorithmIdentifier to be a long[1], thus only integer-based
// algorithms can be negotiated.
//
// [1] https://www.w3.org/TR/webauthn/#alg-identifier
ELEMENT(Is::kRequired, COSEKey, alg),
IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kAlg)),
// kKty is required in COSE keys:
// https://tools.ietf.org/html/rfc8152#section-7
ELEMENT(Is::kRequired, COSEKey, kty),
IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)),
Stop<COSEKey>(),
// clang-format on
};
if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, public_key_map)) {
FIDO_LOG(ERROR) << "Failed to parse COSE key";
return base::nullopt;
}
// In WebIDL, a |long| is an |int32_t|[1].
//
// [1] https://heycam.github.io/webidl/#idl-long
const int64_t algorithm64 = *cose_key.alg;
if (algorithm64 > std::numeric_limits<int32_t>::max() ||
algorithm64 < std::numeric_limits<int32_t>::min()) {
FIDO_LOG(ERROR) << "COSE algorithm in public key is out of range";
return base::nullopt;
}
const int32_t algorithm = static_cast<int32_t>(algorithm64);
const int64_t key_type = *cose_key.kty;
std::unique_ptr<PublicKey> public_key;
if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2) ||
key_type == static_cast<int64_t>(CoseKeyTypes::kOKP)) {
auto curve = public_key_map.find(
cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticCurve)));
if (curve == public_key_map.end() || !curve->second.is_integer()) {
return base::nullopt;
}
const int64_t curve_id = curve->second.GetInteger();
if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2) &&
curve_id == static_cast<int64_t>(CoseCurves::kP256)) {
auto p256_key = P256PublicKey::ExtractFromCOSEKey(
algorithm, public_key_cbor_bytes, public_key_map);
if (!p256_key) {
FIDO_LOG(ERROR) << "Invalid P-256 public key";
return base::nullopt;
}
public_key = std::move(p256_key);
} else if (key_type == static_cast<int64_t>(CoseKeyTypes::kOKP) &&
curve_id == static_cast<int64_t>(CoseCurves::kEd25519)) {
auto ed25519_key = Ed25519PublicKey::ExtractFromCOSEKey(
algorithm, public_key_cbor_bytes, public_key_map);
if (!ed25519_key) {
FIDO_LOG(ERROR) << "Invalid Ed25519 public key";
return base::nullopt;
}
public_key = std::move(ed25519_key);
}
} else if (key_type == static_cast<int64_t>(CoseKeyTypes::kRSA)) {
auto rsa_key = RSAPublicKey::ExtractFromCOSEKey(
algorithm, public_key_cbor_bytes, public_key_map);
if (!rsa_key) {
FIDO_LOG(ERROR) << "Invalid RSA public key";
return base::nullopt;
}
public_key = std::move(rsa_key);
}
if (!public_key) {
public_key = std::make_unique<PublicKey>(algorithm, public_key_cbor_bytes,
base::nullopt);
}
return std::make_pair(
AttestedCredentialData(aaguid, credential_id_length_span,
fido_parsing_utils::Materialize(credential_id),
std::move(public_key)),
buffer);
}
// static
base::Optional<AttestedCredentialData>
AttestedCredentialData::CreateFromU2fRegisterResponse(
base::span<const uint8_t> u2f_data,
std::unique_ptr<PublicKey> public_key) {
// TODO(crbug/799075): Introduce a CredentialID class to do this extraction.
// Extract the length of the credential (i.e. of the U2FResponse key
// handle). Length is big endian.
std::vector<uint8_t> extracted_length =
fido_parsing_utils::Extract(u2f_data, kU2fKeyHandleLengthOffset, 1);
if (extracted_length.empty()) {
return base::nullopt;
}
// For U2F register request, device AAGUID is set to zeros.
std::array<uint8_t, kAaguidLength> aaguid = {};
// Note that U2F responses only use one byte for length.
std::array<uint8_t, kCredentialIdLengthLength> credential_id_length = {
0, extracted_length[0]};
// Extract the credential id (i.e. key handle).
std::vector<uint8_t> credential_id = fido_parsing_utils::Extract(
u2f_data, kU2fKeyHandleOffset,
base::strict_cast<size_t>(credential_id_length[1]));
if (credential_id.empty()) {
return base::nullopt;
}
return AttestedCredentialData(aaguid, credential_id_length,
std::move(credential_id),
std::move(public_key));
}
AttestedCredentialData::AttestedCredentialData(AttestedCredentialData&& other) =
default;
AttestedCredentialData::AttestedCredentialData(
base::span<const uint8_t, kAaguidLength> aaguid,
base::span<const uint8_t, kCredentialIdLengthLength> credential_id_length,
std::vector<uint8_t> credential_id,
std::unique_ptr<PublicKey> public_key)
: aaguid_(fido_parsing_utils::Materialize(aaguid)),
credential_id_length_(
fido_parsing_utils::Materialize(credential_id_length)),
credential_id_(std::move(credential_id)),
public_key_(std::move(public_key)) {}
AttestedCredentialData& AttestedCredentialData::operator=(
AttestedCredentialData&& other) = default;
AttestedCredentialData::~AttestedCredentialData() = default;
bool AttestedCredentialData::IsAaguidZero() const {
return std::all_of(aaguid_.begin(), aaguid_.end(),
[](uint8_t v) { return v == 0; });
}
void AttestedCredentialData::DeleteAaguid() {
std::fill(aaguid_.begin(), aaguid_.end(), 0);
}
std::vector<uint8_t> AttestedCredentialData::SerializeAsBytes() const {
std::vector<uint8_t> attestation_data;
fido_parsing_utils::Append(&attestation_data, aaguid_);
fido_parsing_utils::Append(&attestation_data, credential_id_length_);
fido_parsing_utils::Append(&attestation_data, credential_id_);
fido_parsing_utils::Append(&attestation_data, public_key_->cose_key_bytes);
return attestation_data;
}
const PublicKey* AttestedCredentialData::public_key() const {
return public_key_.get();
}
} // namespace device