Skip to content

Commit

Permalink
add sd-jwt to credential pack (#42)
Browse files Browse the repository at this point in the history
This PR adds support for SD-JWT credentials in credential pack.
  • Loading branch information
Ryanmtate authored Oct 23, 2024
1 parent 8824cb9 commit 1f1e330
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 66 deletions.
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

0 comments on commit 1f1e330

Please sign in to comment.