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 | |
40 | namespace fido { |
41 | using namespace WebCore; |
42 | |
43 | namespace { |
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. |
49 | const uint8_t uncompressedKey = 0x04; |
50 | // https://www.w3.org/TR/webauthn/#flags |
51 | const 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 |
53 | const uint8_t minSignatureLength = 71; |
54 | const 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 |
56 | const size_t flagIndex = 0; |
57 | const size_t counterIndex = 1; |
58 | const size_t signatureIndex = 5; |
59 | |
60 | static 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 | |
80 | static 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 | |
95 | static 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 | |
104 | static 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 | |
124 | static 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 | |
150 | Optional<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 | |
176 | Optional<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 | |