Skip to content
Merged
2 changes: 1 addition & 1 deletion include/TrustWalletCore/TWWebAuthn.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct TWPublicKey *_Nullable TWWebAuthnGetPublicKey(TWData *_Nonnull attestatio
/// \param signature ASN encoded webauthn signature: https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types
/// \return Concatenated r and s values.
TW_EXPORT_STATIC_METHOD
TWData *_Nonnull TWWebAuthnGetRSValues(TWData *_Nonnull signature);
TWData *_Nullable TWWebAuthnGetRSValues(TWData *_Nonnull signature);

/// Reconstructs the original message that was signed via P256 curve. Can be used for signature validation.
///
Expand Down
81 changes: 57 additions & 24 deletions src/WebAuthn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

namespace TW::WebAuthn {

// 32 + 1 flags + 4 counter
static const std::size_t gAuthDataMinSize = 37;
// 16 aaguid + 2 credIDLen
static const std::size_t gAuthCredentialDataMinSize = 18;

// https://www.w3.org/TR/webauthn-2/#authenticator-data
struct AuthData {
Data rpIdHash;
Expand All @@ -30,7 +35,11 @@ struct AuthData {
Data COSEPublicKey;
};

AuthData parseAuthData(const Data& buffer) {
std::optional<AuthData> parseAuthData(const Data& buffer) {
if (buffer.size() < gAuthDataMinSize) {
return std::nullopt;
}

AuthData authData;

authData.rpIdHash = subData(buffer, 0, 32);
Expand All @@ -53,6 +62,10 @@ AuthData parseAuthData(const Data& buffer) {
it += 4;

if (authData.flags.at) {
if (static_cast<size_t>(buffer.end() - it) < gAuthCredentialDataMinSize) {
return std::nullopt;
}

authData.aaguid = Data(it, it + 16);
it += 16;

Expand All @@ -61,6 +74,10 @@ AuthData parseAuthData(const Data& buffer) {
credIDLenBuf[1]);
it += 2;

if (static_cast<size_t>(buffer.end() - it) < static_cast<size_t>(credIDLen)) {
return std::nullopt;
}

authData.credID = Data(it, it + credIDLen);
it += credIDLen;

Expand All @@ -83,31 +100,47 @@ auto findStringKey = [](const auto& map, const auto& key) {
};

std::optional<PublicKey> getPublicKey(const Data& attestationObject) {
const Data authData = findStringKey(TW::Cbor::Decode(attestationObject).getMapElements(), "authData")->second.getBytes();
if (authData.empty()) {
return std::nullopt;
}

const AuthData authDataParsed = parseAuthData(authData);
const auto COSEPublicKey = TW::Cbor::Decode(authDataParsed.COSEPublicKey).getMapElements();

if (COSEPublicKey.empty()) {
try {
const auto attestationObjectElements = TW::Cbor::Decode(attestationObject).getMapElements();
const auto authDataIter = findStringKey(attestationObjectElements, "authData");
if (authDataIter == attestationObjectElements.end()) {
return std::nullopt;
}

const Data authData = authDataIter->second.getBytes();
if (authData.empty()) {
return std::nullopt;
}

const auto authDataParsed = parseAuthData(authData);
if (!authDataParsed.has_value()) {
return std::nullopt;
}
const auto COSEPublicKey = TW::Cbor::Decode(authDataParsed->COSEPublicKey).getMapElements();

if (COSEPublicKey.empty()) {
return std::nullopt;
}

// https://www.w3.org/TR/webauthn-2/#sctn-encoded-credPubKey-examples
const std::string xKey = "-2";
const std::string yKey = "-3";

const auto x = findIntKey(COSEPublicKey, xKey);
const auto y = findIntKey(COSEPublicKey, yKey);
if (x == COSEPublicKey.end() || y == COSEPublicKey.end()) {
return std::nullopt;
}

Data publicKey;
append(publicKey, 0x04);
append(publicKey, x->second.getBytes());
append(publicKey, y->second.getBytes());

return PublicKey(publicKey, TWPublicKeyTypeNIST256p1Extended);
} catch (...) {
return std::nullopt;
}

// https://www.w3.org/TR/webauthn-2/#sctn-encoded-credPubKey-examples
const std::string xKey = "-2";
const std::string yKey = "-3";

const auto x = findIntKey(COSEPublicKey, xKey);
const auto y = findIntKey(COSEPublicKey, yKey);

Data publicKey;
append(publicKey, 0x04);
append(publicKey, x->second.getBytes());
append(publicKey, y->second.getBytes());

return PublicKey(publicKey, TWPublicKeyTypeNIST256p1Extended);
}

Data reconstructSignedMessage(const Data& authenticatorData, const Data& clientDataJSON) {
Expand Down
10 changes: 7 additions & 3 deletions src/interface/TWWebAuthn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ struct TWPublicKey *_Nullable TWWebAuthnGetPublicKey(TWData *_Nonnull attestatio
}
}

TWData *_Nonnull TWWebAuthnGetRSValues(TWData *_Nonnull signature) {
TWData *_Nullable TWWebAuthnGetRSValues(TWData *_Nonnull signature) {
const auto& signatureData = *reinterpret_cast<const TW::Data*>(signature);
const auto& rsValues = TW::ASN::AsnParser::ecdsa_signature_from_der(signatureData);
return TWDataCreateWithData(&rsValues);
const auto maybeRSValues = TW::ASN::AsnParser::ecdsa_signature_from_der(signatureData);
if (!maybeRSValues.has_value()) {
return nullptr;
}
const auto& rsValues = maybeRSValues.value();
return TWDataCreateWithBytes(rsValues.data(), rsValues.size());
}

TWData *_Nonnull TWWebAuthnReconstructOriginalMessage(TWData* _Nonnull authenticatorData, TWData* _Nonnull clientDataJSON) {
Expand Down
2 changes: 1 addition & 1 deletion swift/Tests/WebAuthnTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class WebAuthnTests: XCTestCase {

func testGetRSValues() {
let signature = Data(hexString: "0x30440220766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec022020cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d")!
let result = WebAuthn.getRSValues(signature: signature)
let result = WebAuthn.getRSValues(signature: signature)!
XCTAssertEqual(result.hexString, "766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec20cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d")
}

Expand Down
2 changes: 1 addition & 1 deletion tests/common/WebAuthnTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ TEST(WebAuthn, GetRSValues) {
// C
{
const auto signature = DATA("0x30440220766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec022020cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d");
const auto& rsValuesData = TWWebAuthnGetRSValues(signature.get());
const auto* rsValuesData = TWWebAuthnGetRSValues(signature.get());
const auto& rsValues = hexEncoded(*reinterpret_cast<const Data*>(WRAPD(rsValuesData).get()));
EXPECT_EQ(rsValues, "0x766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec20cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d");
}
Expand Down
Loading