keepassxc

Форк
0
/
BrowserPasskeysClient.cpp 
175 строк · 7.8 Кб
1
/*
2
 *  Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
3
 *
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU General Public License as published by
6
 *  the Free Software Foundation, either version 3 of the License, or
7
 *  (at your option) any later version.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17

18
#include "BrowserPasskeysClient.h"
19
#include "BrowserMessageBuilder.h"
20
#include "BrowserPasskeys.h"
21
#include "PasskeyUtils.h"
22

23
#include <QJsonDocument>
24

25
Q_GLOBAL_STATIC(BrowserPasskeysClient, s_browserPasskeysClient);
26

27
BrowserPasskeysClient* BrowserPasskeysClient::instance()
28
{
29
    return s_browserPasskeysClient;
30
}
31

32
// Constructs CredentialCreationOptions from the original PublicKeyCredential
33
// https://www.w3.org/TR/2019/REC-webauthn-1-20190304/#createCredential
34
int BrowserPasskeysClient::getCredentialCreationOptions(const QJsonObject& publicKeyOptions,
35
                                                        const QString& origin,
36
                                                        QJsonObject* result) const
37
{
38
    if (!result || publicKeyOptions.isEmpty()) {
39
        return ERROR_PASSKEYS_EMPTY_PUBLIC_KEY;
40
    }
41

42
    // Check validity of some basic values
43
    const auto checkResultError = passkeyUtils()->checkLimits(publicKeyOptions);
44
    if (checkResultError > 0) {
45
        return checkResultError;
46
    }
47

48
    // Get effective domain
49
    QString effectiveDomain;
50
    const auto effectiveDomainResponse = passkeyUtils()->getEffectiveDomain(origin, &effectiveDomain);
51
    if (effectiveDomainResponse > 0) {
52
        return effectiveDomainResponse;
53
    }
54

55
    // Validate RP ID
56
    QString rpId;
57
    const auto rpName = publicKeyOptions["rp"]["name"].toString();
58
    const auto rpIdResponse = passkeyUtils()->validateRpId(publicKeyOptions["rp"]["id"], effectiveDomain, &rpId);
59
    if (rpIdResponse > 0) {
60
        return rpIdResponse;
61
    }
62

63
    // Check PublicKeyCredentialTypes
64
    const auto pubKeyCredParams = passkeyUtils()->parseCredentialTypes(publicKeyOptions["pubKeyCredParams"].toArray());
65
    if (pubKeyCredParams.isEmpty() && !publicKeyOptions["pubKeyCredParams"].toArray().isEmpty()) {
66
        return ERROR_PASSKEYS_NO_SUPPORTED_ALGORITHMS;
67
    }
68

69
    // Check Attestation
70
    const auto attestation = passkeyUtils()->parseAttestation(publicKeyOptions["attestation"].toString());
71

72
    // Check validity of AuthenticatorSelection
73
    auto authenticatorSelection = publicKeyOptions["authenticatorSelection"].toObject();
74
    const bool isAuthenticatorSelectionValid = passkeyUtils()->isAuthenticatorSelectionValid(authenticatorSelection);
75
    if (!isAuthenticatorSelectionValid) {
76
        return ERROR_PASSKEYS_WAIT_FOR_LIFETIMER;
77
    }
78

79
    // Add default values for compatibility
80
    if (authenticatorSelection.isEmpty()) {
81
        authenticatorSelection = QJsonObject({{"userVerification", BrowserPasskeys::REQUIREMENT_PREFERRED}});
82
    } else if (authenticatorSelection["userVerification"].toString().isEmpty()) {
83
        authenticatorSelection["userVerification"] = BrowserPasskeys::REQUIREMENT_PREFERRED;
84
    }
85

86
    auto authenticatorAttachment = authenticatorSelection["authenticatorAttachment"].toString();
87
    if (authenticatorAttachment.isEmpty()) {
88
        authenticatorAttachment = BrowserPasskeys::ATTACHMENT_PLATFORM;
89
    }
90

91
    // Unknown values are ignored, but a warning will be still shown just in case
92
    const auto userVerification = authenticatorSelection["userVerification"].toString();
93
    if (!passkeyUtils()->isUserVerificationValid(userVerification)) {
94
        qWarning() << browserMessageBuilder()->getErrorMessage(ERROR_PASSKEYS_INVALID_USER_VERIFICATION);
95
    }
96

97
    // Parse requireResidentKey and userVerification
98
    const auto isResidentKeyRequired = passkeyUtils()->isResidentKeyRequired(authenticatorSelection);
99
    const auto isUserVerificationRequired = passkeyUtils()->isUserVerificationRequired(authenticatorSelection);
100

101
    // Extensions
102
    auto extensionObject = publicKeyOptions["extensions"].toObject();
103
    const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
104
    const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData.extensionData);
105

106
    // Construct the final object
107
    QJsonObject credentialCreationOptions;
108
    credentialCreationOptions["attestation"] = attestation; // Set this, even if only "none" is supported
109
    credentialCreationOptions["authenticatorAttachment"] = authenticatorAttachment;
110
    credentialCreationOptions["clientDataJSON"] = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, false);
111
    credentialCreationOptions["clientExtensionResults"] = extensionData.extensionObject;
112
    credentialCreationOptions["credTypesAndPubKeyAlgs"] = pubKeyCredParams;
113
    credentialCreationOptions["excludeCredentials"] = publicKeyOptions["excludeCredentials"];
114
    credentialCreationOptions["extensions"] = extensions;
115
    credentialCreationOptions["residentKey"] = isResidentKeyRequired;
116
    credentialCreationOptions["rp"] = QJsonObject({{"id", rpId}, {"name", rpName}});
117
    credentialCreationOptions["user"] = publicKeyOptions["user"];
118
    credentialCreationOptions["userPresence"] = !isUserVerificationRequired;
119
    credentialCreationOptions["userVerification"] = isUserVerificationRequired;
120

121
    *result = credentialCreationOptions;
122
    return 0;
123
}
124

125
// Use an existing credential
126
// https://www.w3.org/TR/2019/REC-webauthn-1-20190304/#getAssertion
127
int BrowserPasskeysClient::getAssertionOptions(const QJsonObject& publicKeyOptions,
128
                                               const QString& origin,
129
                                               QJsonObject* result) const
130
{
131
    if (!result || publicKeyOptions.isEmpty()) {
132
        return ERROR_PASSKEYS_EMPTY_PUBLIC_KEY;
133
    }
134

135
    // Get effective domain
136
    QString effectiveDomain;
137
    const auto effectiveDomainResponse = passkeyUtils()->getEffectiveDomain(origin, &effectiveDomain);
138
    if (effectiveDomainResponse > 0) {
139
        return effectiveDomainResponse;
140
    }
141

142
    // Validate RP ID
143
    QString rpId;
144
    const auto rpIdResponse = passkeyUtils()->validateRpId(publicKeyOptions["rpId"], effectiveDomain, &rpId);
145
    if (rpIdResponse > 0) {
146
        return rpIdResponse;
147
    }
148

149
    // Extensions
150
    auto extensionObject = publicKeyOptions["extensions"].toObject();
151
    const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
152
    const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData.extensionData);
153

154
    // clientDataJson
155
    const auto clientDataJson = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, true);
156

157
    // Unknown values are ignored, but a warning will be still shown just in case
158
    const auto userVerification = publicKeyOptions["userVerification"].toString();
159
    if (!passkeyUtils()->isUserVerificationValid(userVerification)) {
160
        qWarning() << browserMessageBuilder()->getErrorMessage(ERROR_PASSKEYS_INVALID_USER_VERIFICATION);
161
    }
162
    const auto isUserVerificationRequired = passkeyUtils()->isUserVerificationRequired(publicKeyOptions);
163

164
    QJsonObject assertionOptions;
165
    assertionOptions["allowCredentials"] = publicKeyOptions["allowCredentials"];
166
    assertionOptions["clientDataJson"] = clientDataJson;
167
    assertionOptions["clientExtensionResults"] = extensionData.extensionObject;
168
    assertionOptions["extensions"] = extensions;
169
    assertionOptions["rpId"] = rpId;
170
    assertionOptions["userPresence"] = true;
171
    assertionOptions["userVerification"] = isUserVerificationRequired;
172

173
    *result = assertionOptions;
174
    return 0;
175
}
176

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.