blob: 66f460137f82a18c579f84a55e50addba2dd8ccd [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/webid/delegation/federated_sd_jwt_handler.h"
#include <string>
#include <vector>
#include "base/barrier_closure.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "content/browser/webid/delegation/jwt_signer.h"
#include "content/browser/webid/delegation/sd_jwt.h"
#include "content/browser/webid/flags.h"
#include "content/browser/webid/mappers.h"
#include "content/browser/webid/request_service.h"
#include "crypto/hash.h"
#include "crypto/keypair.h"
#include "crypto/sha2.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
namespace content {
using blink::mojom::FederatedAuthRequestResult;
namespace {
std::vector<uint8_t> Sha256(std::string_view data) {
auto hash = crypto::hash::Sha256(base::as_byte_span(data));
std::vector<uint8_t> result{hash.begin(), hash.end()};
return result;
}
} // namespace
FederatedSdJwtHandler::FederatedSdJwtHandler(
const blink::mojom::IdentityProviderRequestOptionsPtr& provider,
RenderFrameHost& render_frame_host,
webid::RequestService* federated_auth_request_impl)
: fields_(provider->fields),
nonce_(provider->nonce),
config_url_(provider->config->config_url),
render_frame_host_(&render_frame_host),
federated_auth_request_impl_(federated_auth_request_impl) {
// Creates a throw away private key for a one-time use for
// a single presentation. The public key gets sent to the
// VC issuance endpoint and gets bound to the issued SD-JWT
// by the issuer, delegating the presentation to the holder.
// The browser selectively discloses the fields that were
// requested and binds the audience and the nonce to the
// Key Binding JWT before returning to the verifier.
private_key_ = crypto::keypair::PrivateKey::GenerateEcP256();
}
FederatedSdJwtHandler::~FederatedSdJwtHandler() {}
std::string FederatedSdJwtHandler::ComputeUrlEncodedTokenPostDataForIssuers(
const std::string& account_id) {
return base::StrCat(
{"account_id=", base::EscapeUrlEncodedData(account_id, /*use_plus=*/true),
"&holder_key=",
base::EscapeUrlEncodedData(*GetPublicKey().Serialize(),
/*use_plus=*/true),
"&format=", base::EscapeUrlEncodedData("vc+sd-jwt", /*use_plus=*/true)});
}
void FederatedSdJwtHandler::ProcessSdJwt(const std::string& token) {
// Checked previously.
DCHECK(webid::IsDelegationEnabled());
auto value = sdjwt::SdJwt::Parse(token);
if (!value) {
federated_auth_request_impl_->CompleteRequestWithError(
FederatedAuthRequestResult::kError,
/*token_status=*/std::nullopt,
/*should_delay_callback=*/false);
return;
}
auto sd_jwt = sdjwt::SdJwt::From(*value);
if (!sd_jwt) {
federated_auth_request_impl_->CompleteRequestWithError(
FederatedAuthRequestResult::kError,
/*token_status=*/std::nullopt,
/*should_delay_callback=*/false);
return;
}
// Each of the disclosures is an individual JSON Object.
// Parse them all and use BarrierCallback to get a callback when all
// parsing is done.
auto callback = BarrierClosure(
sd_jwt->disclosures.size(),
base::BindOnce(&FederatedSdJwtHandler::OnSdJwtParsed,
weak_ptr_factory_.GetWeakPtr(), sd_jwt->jwt));
for (const auto& json : sd_jwt->disclosures) {
data_decoder::DataDecoder::ParseJsonIsolated(
json.value(),
base::BindOnce(&FederatedSdJwtHandler::OnDisclosureParsed,
weak_ptr_factory_.GetWeakPtr(), callback, json.value()));
}
}
sdjwt::Jwk FederatedSdJwtHandler::GetPublicKey() const {
return *sdjwt::ExportPublicKey(*private_key_);
}
void FederatedSdJwtHandler::OnDisclosureParsed(
base::RepeatingClosure cb,
const std::string& json,
data_decoder::DataDecoder::ValueOrError result) {
if (!result.has_value() || !result->is_list()) {
cb.Run();
return;
}
auto disclosure = sdjwt::Disclosure::From(result->GetList());
if (!disclosure) {
// Ignore invalid disclosure structures.
cb.Run();
return;
}
disclosures_.push_back({disclosure->name, sdjwt::JSONString(json)});
cb.Run();
}
void FederatedSdJwtHandler::OnSdJwtParsed(const sdjwt::Jwt& jwt) {
std::vector<std::string> fields = {webid::kDefaultFieldName,
webid::kDefaultFieldEmail,
webid::kDefaultFieldPicture};
if (fields_.has_value()) {
fields = fields_.value();
}
auto selected = sdjwt::SdJwt::Disclose(disclosures_, fields);
disclosures_.clear();
if (!selected) {
federated_auth_request_impl_->CompleteRequestWithError(
FederatedAuthRequestResult::kError,
/*token_status=*/std::nullopt,
/*should_delay_callback=*/false);
return;
}
sdjwt::SdJwt result;
result.jwt = jwt;
result.disclosures = *selected;
auto sdjwtkb = sdjwt::SdJwtKb::Create(
result, render_frame_host_->GetLastCommittedOrigin().Serialize(), nonce_,
/*iat=*/base::Time::Now(), base::BindRepeating(Sha256),
sdjwt::CreateJwtSigner(*std::move(private_key_)));
if (!sdjwtkb) {
federated_auth_request_impl_->CompleteRequestWithError(
FederatedAuthRequestResult::kError,
/*token_status=*/std::nullopt,
/*should_delay_callback=*/false);
return;
}
auto token = sdjwtkb->Serialize();
// TODO(crbug.com/380367784): introduce and use a more specific
// TokenStatus type for SD-JWTs.
federated_auth_request_impl_->CompleteRequest(
FederatedAuthRequestResult::kSuccess,
webid::RequestIdTokenStatus::kSuccessUsingTokenInHttpResponse,
/*token_error=*/std::nullopt, config_url_, base::Value(token),
/*should_delay_callback=*/false);
}
} // namespace content