Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import org.multipaz.cbor.Cbor
import org.multipaz.cbor.DataItem
import org.multipaz.cbor.DiagnosticOption
import org.multipaz.cbor.buildCborMap
import org.multipaz.cbor.putCborArray
import org.multipaz.cbor.putCborMap
Expand Down Expand Up @@ -107,14 +108,6 @@ private suspend fun updateCredmanUnlocked(
documentTypeRepository = documentTypeRepository,
selectedProtocols = selectedProtocols
)
/*
Logger.i(TAG, "credentialDatabase: " +
Cbor.toDiagnostics(
item = credentialDatabase,
options = setOf(DiagnosticOption.EMBEDDED_CBOR, DiagnosticOption.PRETTY_PRINT, DiagnosticOption.BSTR_PRINT_LENGTH)
)
)
*/

val credentialDatabaseCbor = Cbor.encode(credentialDatabase)

Expand Down Expand Up @@ -145,6 +138,7 @@ private suspend fun updateCredmanUnlocked(
set(CREDMAN_DB_SHA256_KEY, credDbSha256)
}
}
Logger.dCbor(TAG, "credentialDatabase", credentialDatabase)

val documents = documentStore.listDocuments(sort = true)
for (document in documents) {
Expand Down Expand Up @@ -262,6 +256,26 @@ private suspend fun exportMdocCredential(
document.readerIdentifiers.forEach { add(it.toByteArray()) }
}
}
if (credential.mso.deviceKeyAuthorizedNamespaces.isNotEmpty() ||
credential.mso.deviceKeyAuthorizedDataElements.isNotEmpty()
) {
putCborMap("keyAuthorizations") {
if (credential.mso.deviceKeyAuthorizedNamespaces.isNotEmpty()) {
putCborArray("nameSpaces") {
credential.mso.deviceKeyAuthorizedNamespaces.forEach { add(it) }
}
}
if (credential.mso.deviceKeyAuthorizedDataElements.isNotEmpty()) {
putCborMap("dataElements") {
credential.mso.deviceKeyAuthorizedDataElements.forEach { (namespace, dataElementList) ->
putCborArray(namespace) {
dataElementList.forEach { add(it) }
}
}
}
}
}
}
putCborMap("namespaces") {
for ((namespace, claimsInNamespace) in claims.organizeByNamespace()) {
putCborMap(namespace) {
Expand Down
85 changes: 78 additions & 7 deletions multipaz-dcapi/src/androidMain/matcher/CredentialDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ CredentialDatabase::CredentialDatabase(const uint8_t* encodedDatabase, size_t en
std::vector<std::string> docProtocols = topProtocols;
std::vector<std::vector<uint8_t>> issuerIdentifiers;
std::vector<std::vector<uint8_t>> readerIdentifiers;
std::vector<std::string> keyAuthorizedNamespaces;
std::map<std::string, std::vector<std::string>> keyAuthorizedDataElements;
std::map resultingClaims = std::map<std::string, Claim>();

auto& docProtocolsPtr = cred->get("protocols");
Expand Down Expand Up @@ -77,6 +79,35 @@ CredentialDatabase::CredentialDatabase(const uint8_t* encodedDatabase, size_t en
}
}

const auto& keyAuthPtr = mdoc->get("keyAuthorizations");
if (keyAuthPtr != nullptr && keyAuthPtr->asMap() != nullptr) {
auto keyAuthMap = keyAuthPtr->asMap();
const auto& nsArrPtr = keyAuthMap->get("nameSpaces");
if (nsArrPtr != nullptr && nsArrPtr->asArray() != nullptr) {
auto arr = nsArrPtr->asArray();
for (auto it = arr->begin(); it != arr->end(); ++it) {
if ((*it)->asTstr() != nullptr) {
keyAuthorizedNamespaces.push_back((*it)->asTstr()->value());
}
}
}
const auto& deMapPtr = keyAuthMap->get("dataElements");
if (deMapPtr != nullptr && deMapPtr->asMap() != nullptr) {
auto deMap = deMapPtr->asMap();
for (auto it = deMap->begin(); it != deMap->end(); ++it) {
std::string nsName = it->first->asTstr()->value();
auto deList = it->second->asArray();
if (deList != nullptr) {
for (auto deIt = deList->begin(); deIt != deList->end(); ++deIt) {
if ((*deIt)->asTstr() != nullptr) {
keyAuthorizedDataElements[nsName].push_back((*deIt)->asTstr()->value());
}
}
}
}
}
}

auto namespaces = mdoc->get("namespaces")->asMap();
for (auto j = namespaces->begin(); j != namespaces->end(); ++j) {
auto namespaceName = j->first->asTstr()->value();
Expand Down Expand Up @@ -142,6 +173,8 @@ CredentialDatabase::CredentialDatabase(const uint8_t* encodedDatabase, size_t en
docProtocols,
issuerIdentifiers,
readerIdentifiers,
keyAuthorizedNamespaces,
keyAuthorizedDataElements,
resultingClaims
)
);
Expand All @@ -160,17 +193,52 @@ bool Credential::supportsProtocol(const std::string& protocol) {
Claim* Credential::findMatchingClaim(const DcqlRequestedClaim& requestedClaim) {
auto joinedPath = requestedClaim.joinPath();
auto ret = claims.find(joinedPath);
if (ret == claims.end()) {
return nullptr;
if (ret != claims.end()) {
// Perform value matching, if requested
if (!requestedClaim.values.empty()) {
const std::vector<std::string>& values = requestedClaim.values;
if (std::find(values.begin(), values.end(), ret->second.matchValue) == values.end()) {
return nullptr;
}
}
return &(ret->second);
}
// Perform value matching, if requested

if (!requestedClaim.values.empty()) {
const std::vector<std::string>& values = requestedClaim.values;
if (std::find(values.begin(), values.end(), ret->second.matchValue) == values.end()) {
return nullptr;
return nullptr;
}

// Check KeyAuthorizations for device-signed data elements
if (requestedClaim.path.size() == 2) {
const std::string& ns = requestedClaim.path[0];
const std::string& elem = requestedClaim.path[1];
bool authorized = false;
if (!vcVct.empty() && ns == "org.iso.transactiondata") {
authorized = true;
} else if (std::find(keyAuthorizedNamespaces.begin(), keyAuthorizedNamespaces.end(), ns) != keyAuthorizedNamespaces.end()) {
authorized = true;
} else {
auto it = keyAuthorizedDataElements.find(ns);
if (it != keyAuthorizedDataElements.end()) {
if (std::find(it->second.begin(), it->second.end(), elem) != it->second.end()) {
authorized = true;
}
}
}
if (authorized) {
auto dynIt = dynamicDeviceClaims.find(joinedPath);
if (dynIt == dynamicDeviceClaims.end()) {
auto [newIt, _] = dynamicDeviceClaims.emplace(
joinedPath,
Claim(joinedPath, "", "", "", /* isDeviceSigned = */ true)
);
return &(newIt->second);
}
return &(dynIt->second);
}
}
return &(ret->second);

return nullptr;
}

void Combination::addToCredmanPicker(const Request& request) const {
Expand Down Expand Up @@ -225,6 +293,9 @@ void Combination::addToCredmanPicker(const Request& request) const {
}

for (const auto &claim: match.claims) {
if (claim->isDeviceSigned) {
continue;
}
if (credmanRuntimeVersion >= 2) {
::AddFieldToEntrySet(entryId,
strdup(claim->displayName.c_str()),
Expand Down
63 changes: 51 additions & 12 deletions multipaz-dcapi/src/androidMain/matcher/CredentialDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@
//#include "Request.h"
struct Request;

struct Claim;

struct DcqlRequestedClaim;

struct Claim {
Claim() = default;
Claim(std::string claimName_, std::string displayName_, std::string value_, std::string matchValue_, bool isDeviceSigned_ = false)
: claimName(std::move(claimName_)),
displayName(std::move(displayName_)),
value(std::move(value_)),
matchValue(std::move(matchValue_)),
isDeviceSigned(isDeviceSigned_) {}
~Claim() {}
// For Json-based credentials the claimName is the concatenation of all paths, using "." and for
// Mdoc-based credentials it's namespaceName.dataElementName
std::string claimName;
std::string displayName;
std::string value;
std::string matchValue;
bool isDeviceSigned = false;
};

struct Credential {
std::string title;
std::string subtitle;
Expand All @@ -35,9 +51,42 @@ struct Credential {
// Reader identifiers (AuthorityKeyIdentifiers)
std::vector<std::vector<uint8_t>> readerIdentifiers;

// Key authorizations (for mdoc device-signed data elements)
std::vector<std::string> keyAuthorizedNamespaces;
std::map<std::string, std::vector<std::string>> keyAuthorizedDataElements;

// Maps from claimName to Claim.
std::map<std::string, Claim> claims;

// Claims dynamically created for authorized device-signed data elements
mutable std::map<std::string, Claim> dynamicDeviceClaims;

Credential(
std::string title_,
std::string subtitle_,
std::vector<uint8_t> bitmap_,
std::string documentId_,
std::string mdocDocType_,
std::string vcVct_,
std::vector<std::string> protocols_,
std::vector<std::vector<uint8_t>> issuerIdentifiers_,
std::vector<std::vector<uint8_t>> readerIdentifiers_,
std::vector<std::string> keyAuthorizedNamespaces_,
std::map<std::string, std::vector<std::string>> keyAuthorizedDataElements_,
std::map<std::string, Claim> claims_
) : title(std::move(title_)),
subtitle(std::move(subtitle_)),
bitmap(std::move(bitmap_)),
documentId(std::move(documentId_)),
mdocDocType(std::move(mdocDocType_)),
vcVct(std::move(vcVct_)),
protocols(std::move(protocols_)),
issuerIdentifiers(std::move(issuerIdentifiers_)),
readerIdentifiers(std::move(readerIdentifiers_)),
keyAuthorizedNamespaces(std::move(keyAuthorizedNamespaces_)),
keyAuthorizedDataElements(std::move(keyAuthorizedDataElements_)),
claims(std::move(claims_)) {}

Claim* findMatchingClaim(const DcqlRequestedClaim& claim);

bool supportsProtocol(const std::string& protocol);
Expand All @@ -47,16 +96,6 @@ struct Credential {
void addCredentialToPicker(const Request& request);
};

struct Claim {
~Claim() {}
// For Json-based credentials the claimName is the concatenation of all paths, using "." and for
// Mdoc-based credentials it's namespaceName.dataElementName
std::string claimName;
std::string displayName;
std::string value;
std::string matchValue;
};

struct CredentialDatabase {
CredentialDatabase(const uint8_t* encodedDatabase, size_t encodedDatabaseLength);
//std::vector<std::string> protocols;
Expand Down
10 changes: 9 additions & 1 deletion multipaz-dcapi/src/androidMain/matcher/cppbor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,15 @@ bool prettyPrintInternal(const Item* item, string& out, size_t indent, size_t ma
case SIMPLE:
const Bool* asBool = item->asSimple()->asBool();
const Null* asNull = item->asSimple()->asNull();
const Double* asDouble = item->asSimple()->asDouble();
if (asBool != nullptr) {
out.append(asBool->value() ? "true" : "false");
} else if (asNull != nullptr) {
out.append("null");
} else if (asDouble != nullptr) {
out.append(std::to_string(asDouble->value()));
} else {
return false;
out.append("simple");
}
break;
}
Expand Down Expand Up @@ -364,7 +367,12 @@ bool Simple::operator==(const Simple& other) const& {
case BOOLEAN:
return *asBool() == *(other.asBool());
case NULL_T:
case UNDEFINED_T:
return true;
case DOUBLE_T:
return asDouble()->value() == other.asDouble()->value();
case SIMPLE_VALUE_T:
return asSimpleValue()->value() == other.asSimpleValue()->value();
default:
CHECK(false); // Impossible to get here.
return false;
Expand Down
Loading
Loading