1// Copyright 2018 The Chromium Authors. All rights reserved.
2// Copyright (C) 2019 Apple Inc. All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30#include "config.h"
31#include "U2fResponseConverter.h"
32
33#if ENABLE(WEB_AUTHN)
34
35#include "CommonCryptoDERUtilities.h"
36#include "FidoConstants.h"
37#include "WebAuthenticationConstants.h"
38#include "WebAuthenticationUtils.h"
39
40namespace fido {
41using namespace WebCore;
42
43namespace {
44
45// In a U2F registration response, the key is in X9.62 format:
46// - a constant 0x04 prefix to indicate an uncompressed key
47// - the 32-byte x coordinate
48// - the 32-byte y coordinate.
49const uint8_t uncompressedKey = 0x04;
50// https://www.w3.org/TR/webauthn/#flags
51const uint8_t makeCredentialFlags = 0b01000001; // UP and AT are set.
52// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-response-message-success
53const uint8_t minSignatureLength = 71;
54const uint8_t maxSignatureLength = 73;
55// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#authentication-response-message-success
56const size_t flagIndex = 0;
57const size_t counterIndex = 1;
58const size_t signatureIndex = 5;
59
60static Vector<uint8_t> extractECPublicKeyFromU2fRegistrationResponse(const Vector<uint8_t>& u2fData)
61{
62 size_t pos = kReservedLength;
63 if (u2fData.size() <= pos || u2fData[pos] != uncompressedKey)
64 return { };
65 pos++;
66
67 if (u2fData.size() < pos + 2 * ES256FieldElementLength)
68 return { };
69
70 Vector<uint8_t> x;
71 x.append(u2fData.data() + pos, ES256FieldElementLength);
72 pos += ES256FieldElementLength;
73
74 Vector<uint8_t> y;
75 y.append(u2fData.data() + pos, ES256FieldElementLength);
76
77 return encodeES256PublicKeyAsCBOR(WTFMove(x), WTFMove(y));
78}
79
80static Vector<uint8_t> extractCredentialIdFromU2fRegistrationResponse(const Vector<uint8_t>& u2fData)
81{
82 size_t pos = kU2fKeyHandleLengthOffset;
83 if (u2fData.size() <= pos)
84 return { };
85 size_t credentialIdLength = u2fData[pos];
86 pos++;
87
88 if (u2fData.size() < pos + credentialIdLength)
89 return { };
90 Vector<uint8_t> credentialId;
91 credentialId.append(u2fData.data() + pos, credentialIdLength);
92 return credentialId;
93}
94
95static Vector<uint8_t> createAttestedCredentialDataFromU2fRegisterResponse(const Vector<uint8_t>& u2fData, const Vector<uint8_t>& publicKey)
96{
97 auto credentialId = extractCredentialIdFromU2fRegistrationResponse(u2fData);
98 if (credentialId.isEmpty())
99 return { };
100
101 return buildAttestedCredentialData(Vector<uint8_t>(aaguidLength, 0), credentialId, publicKey);
102}
103
104static size_t parseX509Length(const Vector<uint8_t>& u2fData, size_t offset)
105{
106 if (u2fData.size() <= offset || u2fData[offset] != SequenceMark)
107 return 0;
108 offset++;
109
110 if (u2fData.size() <= offset)
111 return 0;
112 const auto sequenceLengthLength = bytesUsedToEncodedLength(u2fData[offset]);
113
114 if (sequenceLengthLength > sizeof(size_t) || (u2fData.size() < offset + sequenceLengthLength))
115 return 0;
116 size_t sequenceLength = sequenceLengthLength == 1 ? u2fData[offset] : 0;
117 offset++;
118 for (auto i = sequenceLengthLength - 1; i; i--, offset++)
119 sequenceLength += u2fData[offset] << (i - 1) * 8;
120
121 return sequenceLength + sequenceLengthLength + sizeof(SequenceMark);
122}
123
124static cbor::CBORValue::MapValue createFidoAttestationStatementFromU2fRegisterResponse(const Vector<uint8_t>& u2fData, size_t offset)
125{
126 auto x509Length = parseX509Length(u2fData, offset);
127 if (!x509Length || u2fData.size() < offset + x509Length)
128 return { };
129
130 Vector<uint8_t> x509;
131 x509.append(u2fData.data() + offset, x509Length);
132 offset += x509Length;
133
134 Vector<uint8_t> signature;
135 signature.append(u2fData.data() + offset, u2fData.size() - offset);
136 if (signature.size() < minSignatureLength || signature.size() > maxSignatureLength)
137 return { };
138
139 cbor::CBORValue::MapValue attestationStatementMap;
140 attestationStatementMap[cbor::CBORValue("sig")] = cbor::CBORValue(WTFMove(signature));
141 Vector<cbor::CBORValue> cborArray;
142 cborArray.append(cbor::CBORValue(WTFMove(x509)));
143 attestationStatementMap[cbor::CBORValue("x5c")] = cbor::CBORValue(WTFMove(cborArray));
144
145 return attestationStatementMap;
146}
147
148} // namespace
149
150Optional<PublicKeyCredentialData> readU2fRegisterResponse(const String& rpId, const Vector<uint8_t>& u2fData)
151{
152 auto publicKey = extractECPublicKeyFromU2fRegistrationResponse(u2fData);
153 if (publicKey.isEmpty())
154 return WTF::nullopt;
155
156 auto attestedCredentialData = createAttestedCredentialDataFromU2fRegisterResponse(u2fData, publicKey);
157 if (attestedCredentialData.isEmpty())
158 return WTF::nullopt;
159
160 // Extract the credentialId for packing into the response data.
161 auto credentialId = extractCredentialIdFromU2fRegistrationResponse(u2fData);
162 ASSERT(!credentialId.isEmpty());
163
164 // The counter is zeroed out for Register requests.
165 auto authData = buildAuthData(rpId, makeCredentialFlags, 0, attestedCredentialData);
166
167 auto fidoAttestationStatement = createFidoAttestationStatementFromU2fRegisterResponse(u2fData, kU2fKeyHandleOffset + credentialId.size());
168 if (fidoAttestationStatement.empty())
169 return WTF::nullopt;
170
171 auto attestationObject = buildAttestationObject(WTFMove(authData), "fido-u2f", WTFMove(fidoAttestationStatement));
172
173 return PublicKeyCredentialData { ArrayBuffer::create(credentialId.data(), credentialId.size()), true, nullptr, ArrayBuffer::create(attestationObject.data(), attestationObject.size()), nullptr, nullptr, nullptr, WTF::nullopt };
174}
175
176Optional<PublicKeyCredentialData> readU2fSignResponse(const String& rpId, const Vector<uint8_t>& keyHandle, const Vector<uint8_t>& u2fData)
177{
178 if (keyHandle.isEmpty() || u2fData.size() <= signatureIndex)
179 return WTF::nullopt;
180
181 // 1 byte flags, 4 bytes counter
182 auto flags = u2fData[flagIndex];
183 uint32_t counter = u2fData[counterIndex] << 24;
184 counter += u2fData[counterIndex + 1] << 16;
185 counter += u2fData[counterIndex + 2] << 8;
186 counter += u2fData[counterIndex + 3];
187 auto authData = buildAuthData(rpId, flags, counter, { });
188
189 return PublicKeyCredentialData { ArrayBuffer::create(keyHandle.data(), keyHandle.size()), false, nullptr, nullptr, ArrayBuffer::create(authData.data(), authData.size()), ArrayBuffer::create(u2fData.data() + signatureIndex, u2fData.size() - signatureIndex), nullptr, WTF::nullopt };
190}
191
192} // namespace fido
193
194#endif // ENABLE(WEB_AUTHN)
195