Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add sd-jwt to credential pack #42

Merged
merged 3 commits into from
Oct 23, 2024
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
65 changes: 48 additions & 17 deletions Sources/MobileSdk/Credential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ open class Credential: Identifiable {

open func get(keys: [String]) -> [String: GenericJSON] {
if keys.contains("id") {
return ["id": GenericJSON.string(self.id)]
return ["id": GenericJSON.string(id)]
} else {
return [:]
}
}
}

extension Mdoc {
public extension Mdoc {
/// Access all of the elements in the mdoc, ignoring namespaces and missing elements that cannot be encoded as JSON.
public func jsonEncodedDetails() -> [String: GenericJSON] {
self.jsonEncodedDetailsInternal(containing: nil)
func jsonEncodedDetails() -> [String: GenericJSON] {
jsonEncodedDetailsInternal(containing: nil)
}

/// Access the specified elements in the mdoc, ignoring namespaces and missing elements that cannot be encoded as
/// JSON.
public func jsonEncodedDetails(containing elementIdentifiers: [String]) -> [String: GenericJSON] {
self.jsonEncodedDetailsInternal(containing: elementIdentifiers)
func jsonEncodedDetails(containing elementIdentifiers: [String]) -> [String: GenericJSON] {
jsonEncodedDetailsInternal(containing: elementIdentifiers)
}

private func jsonEncodedDetailsInternal(containing elementIdentifiers: [String]?) -> [String: GenericJSON] {
// Ignore the namespaces.
Dictionary(uniqueKeysWithValues: self.details().flatMap {
Dictionary(uniqueKeysWithValues: details().flatMap {
$1.compactMap {
let id = $0.identifier

Expand All @@ -55,10 +55,10 @@ extension Mdoc {
}
}

extension JwtVc {
public extension JwtVc {
/// Access the W3C VCDM credential (not including the JWT envelope).
public func credentialClaims() -> [String: GenericJSON] {
if let data = self.credentialAsJsonEncodedUtf8String().data(using: .utf8) {
func credentialClaims() -> [String: GenericJSON] {
if let data = credentialAsJsonEncodedUtf8String().data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
if let object = json.dictValue {
Expand All @@ -75,17 +75,17 @@ extension JwtVc {
}

/// Access the specified claims from the W3C VCDM credential (not including the JWT envelope).
public func credentialClaims(containing claimNames: [String]) -> [String: GenericJSON] {
self.credentialClaims().filter { (key, _) in
func credentialClaims(containing claimNames: [String]) -> [String: GenericJSON] {
credentialClaims().filter { key, _ in
claimNames.contains(key)
}
}
}

extension JsonVc {
public extension JsonVc {
/// Access the W3C VCDM credential
public func credentialClaims() -> [String: GenericJSON] {
if let data = self.credentialAsJsonEncodedUtf8String().data(using: .utf8) {
func credentialClaims() -> [String: GenericJSON] {
if let data = credentialAsJsonEncodedUtf8String().data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
if let object = json.dictValue {
Expand All @@ -102,8 +102,39 @@ extension JsonVc {
}

/// Access the specified claims from the W3C VCDM credential.
public func credentialClaims(containing claimNames: [String]) -> [String: GenericJSON] {
self.credentialClaims().filter { (key, _) in
func credentialClaims(containing claimNames: [String]) -> [String: GenericJSON] {
credentialClaims().filter { key, _ in
claimNames.contains(key)
}
}
}

public extension Vcdm2SdJwt {
/// Access the SD-JWT decoded credential
func credentialClaims() -> [String: GenericJSON] {
do {
if let data = try revealedClaimsAsJsonString().data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
if let object = json.dictValue {
return object
} else {
print("unexpected format for SD-JWT")
}
} catch let error as NSError {
print("failed to decode as JSON: \(error)")
}
}
} catch let error as NSError {
print("failed to decode SD-JWT data from UTF-8: \(error)")
}

return [:]
}

/// Access the specified claims from the SD-JWT credential.
func credentialClaims(containing claimNames: [String]) -> [String: GenericJSON] {
credentialClaims().filter { key, _ in
claimNames.contains(key)
}
}
Expand Down
98 changes: 55 additions & 43 deletions Sources/MobileSdk/CredentialPack.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import CryptoKit
import Foundation
import SpruceIDMobileSdkRs
import CryptoKit

public class CredentialPack {

private var credentials: [ParsedCredential]

/// Initialize an empty CredentialPack.
public init() {
self.credentials = []
credentials = []
}

/// Initialize a CredentialPack from existing credentials.
Expand All @@ -18,73 +17,86 @@ public class CredentialPack {

/// Add a JwtVc to the CredentialPack.
public func addJwtVc(jwtVc: JwtVc) -> [ParsedCredential] {
self.credentials.append(ParsedCredential.newJwtVcJson(jwtVc: jwtVc))
return self.credentials
credentials.append(ParsedCredential.newJwtVcJson(jwtVc: jwtVc))
return credentials
}

/// Add a JsonVc to the CredentialPack.
public func addJsonVc(jsonVc: JsonVc) -> [ParsedCredential] {
self.credentials.append(ParsedCredential.newLdpVc(jsonVc: jsonVc))
return self.credentials
credentials.append(ParsedCredential.newLdpVc(jsonVc: jsonVc))
return credentials
}

/// Add an SD-JWT to the CredentialPack.
public func addSdJwt(sdJwt: Vcdm2SdJwt) -> [ParsedCredential] {
credentials.append(ParsedCredential.newSdJwt(sdJwtVc: sdJwt))
return credentials
}

/// Add an Mdoc to the CredentialPack.
public func addMDoc(mdoc: Mdoc) -> [ParsedCredential] {
self.credentials.append(ParsedCredential.newMsoMdoc(mdoc: mdoc))
return self.credentials
credentials.append(ParsedCredential.newMsoMdoc(mdoc: mdoc))
return credentials
}

/// Find credential claims from all credentials in this CredentialPack.
public func findCredentialClaims(claimNames: [String]) -> [Uuid: [String: GenericJSON]] {
Dictionary(uniqueKeysWithValues: self.list()
.map { credential in
var claims: [String: GenericJSON]
if let mdoc = credential.asMsoMdoc() {
if claimNames.isEmpty {
claims = mdoc.jsonEncodedDetails()
Dictionary(
uniqueKeysWithValues: list()
.map { credential in
var claims: [String: GenericJSON]
if let mdoc = credential.asMsoMdoc() {
if claimNames.isEmpty {
claims = mdoc.jsonEncodedDetails()
} else {
claims = mdoc.jsonEncodedDetails(containing: claimNames)
}
} else if let jwtVc = credential.asJwtVc() {
if claimNames.isEmpty {
claims = jwtVc.credentialClaims()
} else {
claims = jwtVc.credentialClaims(containing: claimNames)
}
} else if let jsonVc = credential.asJsonVc() {
if claimNames.isEmpty {
claims = jsonVc.credentialClaims()
} else {
claims = jsonVc.credentialClaims(containing: claimNames)
}
} else if let sdJwt = credential.asSdJwt() {
if claimNames.isEmpty {
claims = sdJwt.credentialClaims()
} else {
claims = sdJwt.credentialClaims(containing: claimNames)
}
} else {
claims = mdoc.jsonEncodedDetails(containing: claimNames)
}
} else if let jwtVc = credential.asJwtVc() {
if claimNames.isEmpty {
claims = jwtVc.credentialClaims()
} else {
claims = jwtVc.credentialClaims(containing: claimNames)
}
} else if let jsonVc = credential.asJsonVc() {
if claimNames.isEmpty {
claims = jsonVc.credentialClaims()
} else {
claims = jsonVc.credentialClaims(containing: claimNames)
}
} else {
var type: String
do {
type = try credential.intoGenericForm().type
} catch {
type = "unknown"
var type: String
do {
type = try credential.intoGenericForm().type
} catch {
type = "unknown"
}
print("unsupported credential type: \(type)")
claims = [:]
}
print("unsupported credential type: \(type)")
claims = [:]
}
return (credential.id(), claims)
})
return (credential.id(), claims)
})
}

/// Get credentials by id.
public func get(credentialsIds: [Uuid]) -> [ParsedCredential] {
return self.credentials.filter {
return credentials.filter {
credentialsIds.contains($0.id())
}
}

/// Get a credential by id.
public func get(credentialId: Uuid) -> ParsedCredential? {
return self.credentials.first(where: { $0.id() == credentialId })
return credentials.first(where: { $0.id() == credentialId })
}

/// List all of the credentials in the CredentialPack.
public func list() -> [ParsedCredential] {
return self.credentials
return credentials
}
}
10 changes: 5 additions & 5 deletions Sources/MobileSdk/Credentials.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ public class CredentialStore {
self.credentials = credentials
}

public func presentMdocBLE(deviceEngagement: DeviceEngagement,
public func presentMdocBLE(deviceEngagement _: DeviceEngagement,
callback: BLESessionStateDelegate,
useL2CAP: Bool = true
// , trustedReaders: TrustedReaders
) async -> IsoMdlPresentation? {
if let firstMdoc = self.credentials.first(where: { $0.asMsoMdoc() != nil }) {
if let firstMdoc = credentials.first(where: { $0.asMsoMdoc() != nil }) {
let mdoc = firstMdoc.asMsoMdoc()!
return await IsoMdlPresentation(mdoc: MDoc(Mdoc: mdoc),
engagement: DeviceEngagement.QRCode,
callback: callback,
useL2CAP: useL2CAP)
engagement: DeviceEngagement.QRCode,
callback: callback,
useL2CAP: useL2CAP)
} else {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ options:
packages:
SpruceIDMobileSdkRs:
url: https://github.com/spruceid/mobile-sdk-rs
revision: "d559d750dbd5945eace315a05ce05b2702ed2865"
from: 0.2.1
SwiftAlgorithms:
url: https://github.com/apple/swift-algorithms
from: 1.2.0
Expand Down
Loading