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

Base Card, PDF417 and MRZ Scanners #29

Merged
merged 12 commits into from
Aug 27, 2024
3 changes: 3 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ excluded:
disabled_rules:
- cyclomatic_complexity
- todo
- file_length
- force_try
- non_optional_string_data_conversion
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let package = Package(
],
dependencies: [
// .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", .branch("main")),
.package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.27"),
.package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.28"),
// .package(path: "../mobile-sdk-rs"),
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0")
],
Expand Down
10 changes: 9 additions & 1 deletion Sources/MobileSdk/Credential.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import Foundation

public class Credential: Identifiable {
open class Credential: Identifiable {
public var id: String

public init(id: String) {
self.id = id
}

open func get(keys: [String]) -> [String: GenericJSON] {
if keys.contains("id") {
return ["id": GenericJSON.string(self.id)]
} else {
return [:]
}
}
}
71 changes: 71 additions & 0 deletions Sources/MobileSdk/CredentialPack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Foundation
import CryptoKit

public class CredentialPack {

private var credentials: [Credential]

public init() {
self.credentials = []
}

public init(credentials: [Credential]) {
self.credentials = credentials
}

public func addW3CVC(credentialString: String) throws -> [Credential]? {
do {
let credential = try W3CVC(credentialString: credentialString)
self.credentials.append(credential)
return self.credentials
} catch {
throw error
}
}

public func addMDoc(mdocBase64: String, keyPEM: String) throws -> [Credential]? {
do {
let mdocData = Data(base64Encoded: mdocBase64)!
let key = try P256.Signing.PrivateKey(pemRepresentation: keyPEM)
let attributes = [kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPrivate] as [String: Any]
let secKey = SecKeyCreateWithData(key.x963Representation as CFData,
attributes as CFDictionary,
nil)!
let query = [kSecClass: kSecClassKey,
kSecAttrApplicationLabel: "mdoc_key",
Juliano1612 marked this conversation as resolved.
Show resolved Hide resolved
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
kSecUseDataProtectionKeychain: true,
kSecValueRef: secKey] as [String: Any]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
print("Status store item: \(status.description)")
let credential = MDoc(fromMDoc: mdocData, namespaces: [:], keyAlias: "mdoc_key")!
self.credentials.append(credential)
return self.credentials
} catch {
throw error
}
}

public func get(keys: [String]) -> [String: [String: GenericJSON]] {
var values: [String: [String: GenericJSON]] = [:]
for cred in self.credentials {
values[cred.id] = cred.get(keys: keys)
}

return values
}

public func get(credentialsIds: [String]) -> [Credential] {
return self.credentials.filter { credentialsIds.contains($0.id) }
}

public func get(credentialId: String) -> Credential? {
if let credential = self.credentials.first(where: { $0.id == credentialId }) {
return credential
} else {
return nil
}
}
}
139 changes: 139 additions & 0 deletions Sources/MobileSdk/GenericJSON.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// GenericJSON implementation based on https://github.com/iwill/generic-json-swift
w4ll3 marked this conversation as resolved.
Show resolved Hide resolved
import Foundation

public enum GenericJSON {
case string(String)
case number(Double)
case object([String: GenericJSON])
case array([GenericJSON])
case bool(Bool)
case null
}

extension GenericJSON: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .array(array):
try container.encode(array)
case let .object(object):
try container.encode(object)
case let .string(string):
try container.encode(string)
case let .number(number):
try container.encode(number)
case let .bool(bool):
try container.encode(bool)
case .null:
try container.encodeNil()
}
}

public func toString() -> String {
switch self {
case .string(let str):
return str
case .number(let num):
return num.debugDescription
case .bool(let bool):
return bool.description
case .null:
return "null"
default:
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try! String(data: encoder.encode(self), encoding: .utf8)!
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let object = try? container.decode([String: GenericJSON].self) {
self = .object(object)
} else if let array = try? container.decode([GenericJSON].self) {
self = .array(array)
} else if let string = try? container.decode(String.self) {
self = .string(string)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let number = try? container.decode(Double.self) {
self = .number(number)
} else if container.decodeNil() {
self = .null
} else {
throw DecodingError.dataCorrupted(
.init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.")
)
}
}
}

extension GenericJSON: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .string(let str):
return str.debugDescription
case .number(let num):
return num.debugDescription
case .bool(let bool):
return bool.description
case .null:
return "null"
default:
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try! String(data: encoder.encode(self), encoding: .utf8)!
}
}
}

public extension GenericJSON {
var dictValue: [String: GenericJSON]? {
if case .object(let value) = self {
return value
}
return nil
}
var arrayValue: [GenericJSON]? {
if case .array(let value) = self {
return value
}
return nil
}
subscript(index: Int) -> GenericJSON? {
if case .array(let arr) = self, arr.indices.contains(index) {
return arr[index]
}
return nil
}

subscript(key: String) -> GenericJSON? {
if case .object(let dict) = self {
return dict[key]
}
return nil
}

subscript(dynamicMember member: String) -> GenericJSON? {
return self[member]
}

subscript(keyPath keyPath: String) -> GenericJSON? {
return queryKeyPath(keyPath.components(separatedBy: "."))
}

func queryKeyPath<T>(_ path: T) -> GenericJSON? where T: Collection, T.Element == String {
guard case .object(let object) = self else {
return nil
}
guard let head = path.first else {
return nil
}
guard let value = object[head] else {
return nil
}
let tail = path.dropFirst()
return tail.isEmpty ? value : value.queryKeyPath(tail)
}

}
36 changes: 36 additions & 0 deletions Sources/MobileSdk/W3CVC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

enum W3CError: Error {
case initializationError(String)
}

public class W3CVC: Credential {
private let credentialString: String
private let credential: GenericJSON?

public init(credentialString: String) throws {
self.credentialString = credentialString
if let data = credentialString.data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
self.credential = json
super.init(id: json["id"]!.toString())
} catch let error as NSError {
throw error
}
} else {
self.credential = nil
super.init(id: "")
throw W3CError.initializationError("Failed to process credential string.")
}
}

override public func get(keys: [String]) -> [String: GenericJSON] {
if let cred = credential!.dictValue {
return cred.filter { keys.contains($0.key) }
} else {
return [:]
}

}
}
Loading
Loading