mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-09 05:27:57 -05:00
* [INJIMOB-3626] refactor: update OVP module as per renaming and output changes Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com> * [INJIMOB-3626] refactor: update OVP module as per renaming and output changes Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com> * [INJIMOB-3626] chore: update inji-openid4vp-ios-swift lib version Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com> --------- Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>
341 lines
14 KiB
Swift
341 lines
14 KiB
Swift
import Foundation
|
|
import OpenID4VP
|
|
import React
|
|
|
|
@objc(InjiOpenID4VP)
|
|
class RNOpenId4VpModule: NSObject, RCTBridgeModule {
|
|
|
|
private var openID4VP: OpenID4VP?
|
|
|
|
static func moduleName() -> String {
|
|
return "InjiOpenID4VP"
|
|
}
|
|
|
|
@objc
|
|
func `initSdk`(_ appId: String, walletMetadata: AnyObject?,resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
do {
|
|
let walletMetadataObject = try getWalletMetadataFromDict(walletMetadata, reject: reject)
|
|
openID4VP = OpenID4VP(traceabilityId: appId, walletMetadata: walletMetadataObject)
|
|
resolve(true)
|
|
} catch {
|
|
reject("OPENID4VP", error.localizedDescription, error)
|
|
}
|
|
}
|
|
|
|
@objc
|
|
func authenticateVerifier(_ urlEncodedAuthorizationRequest: String,
|
|
trustedVerifierJSON: AnyObject,
|
|
shouldValidateClient: Bool,
|
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
Task {
|
|
do {
|
|
guard let verifierMeta = trustedVerifierJSON as? [[String:Any]] else {
|
|
reject("OPENID4VP", "Invalid verifier meta format", nil)
|
|
return
|
|
}
|
|
|
|
let trustedVerifiersList: [Verifier] = try parseVerifiers(verifierMeta)
|
|
|
|
let authenticationResponse: AuthorizationRequest = try await openID4VP!.authenticateVerifier(
|
|
urlEncodedAuthorizationRequest: urlEncodedAuthorizationRequest,
|
|
trustedVerifierJSON: trustedVerifiersList,
|
|
shouldValidateClient: shouldValidateClient
|
|
)
|
|
|
|
let response = try toJsonString(jsonObject: authenticationResponse)
|
|
resolve(response)
|
|
} catch {
|
|
rejectWithOpenID4VPError(error, reject: reject)
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc
|
|
func constructUnsignedVPToken(_ credentialsMap: AnyObject,
|
|
holderId: String,
|
|
signatureSuite: String,
|
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
Task {
|
|
do {
|
|
guard let credentialsMap = credentialsMap as? [String: [String: [Any]]] else {
|
|
reject("OPENID4VP", "Invalid credentials map format", nil)
|
|
return
|
|
}
|
|
|
|
let formattedCredentialsMap: [String: [FormatType: [AnyCodable]]] = credentialsMap.mapValues { selectedVcsFormatMap -> [FormatType: [AnyCodable]] in
|
|
selectedVcsFormatMap.reduce(into: [:]) { result, entry in
|
|
let (credentialFormat, credentialsArray) = entry
|
|
switch FormatType(rawValue: credentialFormat) {
|
|
case .ldp_vc:
|
|
result[.ldp_vc] = credentialsArray.map { AnyCodable($0) }
|
|
case .mso_mdoc:
|
|
result[.mso_mdoc] = credentialsArray.map { AnyCodable($0) }
|
|
case .dc_sd_jwt:
|
|
result[.dc_sd_jwt] = credentialsArray.map { AnyCodable($0) }
|
|
case .vc_sd_jwt:
|
|
result[.vc_sd_jwt] = credentialsArray.map { AnyCodable($0) }
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
let response = try await openID4VP?.constructUnsignedVPToken(
|
|
verifiableCredentials: formattedCredentialsMap,
|
|
holderId: holderId,
|
|
signatureSuite: signatureSuite
|
|
)
|
|
|
|
let encodableDict = response?.mapKeys { $0.rawValue }.mapValues { EncodableWrapper($0) }
|
|
let jsonData = try JSONEncoder().encode(encodableDict)
|
|
|
|
if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
|
|
resolve(jsonObject)
|
|
} else {
|
|
reject("ERROR", "Failed to serialize JSON", nil)
|
|
}
|
|
} catch {
|
|
rejectWithOpenID4VPError(error, reject: reject)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc
|
|
func shareVerifiablePresentation(_ vpTokenSigningResults: [String: Any],
|
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
Task {
|
|
do {
|
|
var formattedVPTokenSigningResults: [FormatType: VPTokenSigningResult] = [:]
|
|
|
|
for (credentialFormat, vpTokenSigningResult) in vpTokenSigningResults {
|
|
switch credentialFormat {
|
|
case FormatType.ldp_vc.rawValue:
|
|
guard let vpResponse = vpTokenSigningResult as? [String: Any],
|
|
let signatureAlgorithm = vpResponse["signatureAlgorithm"] as? String else {
|
|
reject("OPENID4VP", "Invalid VP token signing result for LDP_VC", nil)
|
|
return
|
|
}
|
|
|
|
let jws = vpResponse["jws"] as? String
|
|
let proofValue = vpResponse["proofValue"] as? String
|
|
formattedVPTokenSigningResults[.ldp_vc] = LdpVPTokenSigningResult(jws: jws, proofValue: proofValue, signatureAlgorithm: signatureAlgorithm)
|
|
|
|
case FormatType.mso_mdoc.rawValue:
|
|
var docTypeToDeviceAuthentication : [String: DeviceAuthentication] = [:]
|
|
guard let vpResponse = vpTokenSigningResult as? [String:[String: String]] else {
|
|
reject("OPENID4VP", "Invalid VP token signing result format", nil)
|
|
return
|
|
}
|
|
for (docType, deviceAuthentication) in vpResponse {
|
|
guard let signature = deviceAuthentication["signature"],
|
|
let algorithm = deviceAuthentication["mdocAuthenticationAlgorithm"] else {
|
|
reject("OPENID4VP", "Invalid VP token signing result provided for mdoc format", nil)
|
|
return
|
|
}
|
|
docTypeToDeviceAuthentication[docType] = DeviceAuthentication(signature: signature, algorithm: algorithm)
|
|
}
|
|
formattedVPTokenSigningResults[.mso_mdoc] = MdocVPTokenSigningResult(docTypeToDeviceAuthentication: docTypeToDeviceAuthentication)
|
|
|
|
case FormatType.vc_sd_jwt.rawValue :
|
|
guard let vpResponse = vpTokenSigningResult as? [String:String] else {
|
|
reject("OPENID4VP", "Invalid VP token signing result format", nil)
|
|
return
|
|
}
|
|
formattedVPTokenSigningResults[.vc_sd_jwt] = SdJwtVpTokenSigningResult(uuidToKbJWTSignature: vpResponse)
|
|
case FormatType.dc_sd_jwt.rawValue :
|
|
guard let vpResponse = vpTokenSigningResult as? [String:String] else {
|
|
reject("OPENID4VP", "Invalid VP token signing result format", nil)
|
|
return
|
|
}
|
|
formattedVPTokenSigningResults[.dc_sd_jwt] = SdJwtVpTokenSigningResult(uuidToKbJWTSignature: vpResponse)
|
|
|
|
default:
|
|
let error = NSError(domain: "Credential format '\(credentialFormat)' is not supported", code: 0)
|
|
rejectWithOpenID4VPError(error, reject: reject)
|
|
return
|
|
}
|
|
}
|
|
|
|
let verifierResponse = try await openID4VP?.sendVPResponseToVerifier(vpTokenSigningResults: formattedVPTokenSigningResults)
|
|
try resolveToJsonData(verifierResponse, resolver: resolve, rejecter: reject)
|
|
} catch {
|
|
rejectWithOpenID4VPError(error, reject: reject)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc
|
|
func sendErrorToVerifier(_ error: String, _ errorCode: String,
|
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
Task {
|
|
let exception: OpenID4VPException = {
|
|
switch errorCode {
|
|
case OpenID4VPErrorCodes.accessDenied:
|
|
return AccessDenied(message: error, className: Self.moduleName())
|
|
case OpenID4VPErrorCodes.invalidTransactionData:
|
|
return InvalidTransactionData(message: error, className: Self.moduleName())
|
|
default:
|
|
return GenericFailure(message: error, className: Self.moduleName())
|
|
}
|
|
}()
|
|
|
|
do {
|
|
let verifierResponse = try await openID4VP?.sendErrorInfoToVerifier(error: exception)
|
|
try resolveToJsonData(verifierResponse, resolver: resolve, rejecter: reject)
|
|
} catch {
|
|
rejectWithOpenID4VPError(error, reject: reject)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func parseVerifiers(_ verifiers: [[String: Any]]) throws -> [Verifier] {
|
|
return try verifiers.map { verifierDict in
|
|
guard let clientId = verifierDict["client_id"] as? String,
|
|
let responseUris = verifierDict["response_uris"] as? [String] else {
|
|
throw NSError(domain: "OpenID4VP", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid Verifier data"])
|
|
}
|
|
|
|
let jwksUri: String? = verifierDict["jwks_uri"] as? String
|
|
|
|
if let allowUnsignedRequest = verifierDict["allow_unsigned_request"] as? Bool {
|
|
return Verifier(clientId: clientId, responseUris: responseUris, jwksUri: jwksUri, allowUnsignedRequest: allowUnsignedRequest)
|
|
}
|
|
|
|
return Verifier(clientId: clientId, responseUris: responseUris,jwksUri: jwksUri)
|
|
}
|
|
}
|
|
|
|
func toJsonString(jsonObject: AuthorizationRequest) throws -> String {
|
|
let encoder = JSONEncoder()
|
|
encoder.keyEncodingStrategy = .convertToSnakeCase
|
|
let jsonData = try encoder.encode(jsonObject)
|
|
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
|
|
throw NSError(domain: "OPENID4VP", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unable to encode JSON"])
|
|
}
|
|
return jsonString
|
|
}
|
|
|
|
@objc
|
|
static func requiresMainQueueSetup() -> Bool {
|
|
return true
|
|
}
|
|
|
|
func rejectWithOpenID4VPError(_ error: Error, reject: RCTPromiseRejectBlock) {
|
|
if let openidError = error as? OpenID4VPException {
|
|
let errorMap: [String: Any] = [
|
|
"errorCode": openidError.errorCode,
|
|
"message": openidError.message,
|
|
"verifierResponse": Inji.toJsonString(openidError.verifierResponse) ?? ""
|
|
]
|
|
let nsError = NSError(
|
|
domain: "OPENID4VP",
|
|
code: 0,
|
|
userInfo: errorMap
|
|
)
|
|
reject(openidError.errorCode, openidError.message, nsError)
|
|
} else {
|
|
let nsError = NSError(domain: error.localizedDescription, code: 0)
|
|
reject("ERR_UNKNOWN", nsError.localizedDescription, nsError)
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
struct EncodableWrapper: Encodable {
|
|
private let value: Encodable
|
|
init(_ value: Encodable) {
|
|
self.value = value
|
|
}
|
|
func encode(to encoder: Encoder) throws {
|
|
try value.encode(to: encoder)
|
|
}
|
|
}
|
|
|
|
extension Dictionary {
|
|
func mapKeys<T: Hashable>(_ transform: (Key) -> T) -> [T: Value] {
|
|
Dictionary<T, Value>(uniqueKeysWithValues: map { (transform($0.key), $0.value) })
|
|
}
|
|
}
|
|
|
|
func getWalletMetadataFromDict(_ walletMetadata: Any,
|
|
reject: RCTPromiseRejectBlock) throws -> WalletMetadata {
|
|
guard let metadata = walletMetadata as? [String: Any] else {
|
|
reject("OPENID4VP", "Invalid wallet metadata format", nil)
|
|
throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid Wallet Metadata"])
|
|
}
|
|
|
|
var vpFormatsSupported: [VPFormatType: VPFormatSupported] = [:]
|
|
if let vpFormatsSupportedDict = metadata["vp_formats_supported"] as? [String: Any] {
|
|
for (format, formatDict) in vpFormatsSupportedDict {
|
|
guard let formatType = VPFormatType.fromValue(format) else {
|
|
throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unsupported VP format: \(format)"])
|
|
}
|
|
if let formatDetails = formatDict as? [String: Any] {
|
|
let algValuesSupported = formatDetails["alg_values_supported"] as? [String]
|
|
vpFormatsSupported[formatType] = VPFormatSupported(algValuesSupported: algValuesSupported)
|
|
} else {
|
|
vpFormatsSupported[formatType] = VPFormatSupported(algValuesSupported: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
let walletMetadataObject = try WalletMetadata(
|
|
presentationDefinitionURISupported: metadata["presentation_definition_uri_supported"] as? Bool ?? true,
|
|
vpFormatsSupported: vpFormatsSupported,
|
|
clientIdSchemesSupported: mapStringsToEnum(metadata["client_id_schemes_supported"] as? [String] ?? [], using: ClientIdScheme.fromValue),
|
|
requestObjectSigningAlgValuesSupported: mapStringsToEnum(metadata["request_object_signing_alg_values_supported"] as? [String] ?? [], using: RequestSigningAlgorithm.fromValue),
|
|
authorizationEncryptionAlgValuesSupported: mapStringsToEnum(metadata["authorization_encryption_alg_values_supported"] as? [String] ?? [], using: KeyManagementAlgorithm.fromValue),
|
|
authorizationEncryptionEncValuesSupported: mapStringsToEnum(metadata["authorization_encryption_enc_values_supported"] as? [String] ?? [], using: ContentEncryptionAlgorithm.fromValue),
|
|
responseTypesSupported: mapStringsToEnum(metadata["response_types_supported"] as? [String] ?? [], using: ResponseType.fromValue)
|
|
)
|
|
return walletMetadataObject
|
|
}
|
|
|
|
func mapStringsToEnum<T: RawRepresentable>(
|
|
_ input: [String],
|
|
using fromValue: (String) -> T?
|
|
) throws -> [T] where T.RawValue == String {
|
|
return try input.map { str in
|
|
guard let match = fromValue(str) else {
|
|
throw NSError(
|
|
domain: "EnumMappingError",
|
|
code: 1001,
|
|
userInfo: [NSLocalizedDescriptionKey: "Invalid value '\(str)' for enum \(T.self)"]
|
|
)
|
|
}
|
|
return match
|
|
}
|
|
}
|
|
|
|
fileprivate func resolveToJsonData(_ response: VerifierResponse?,resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
rejecter reject: @escaping RCTPromiseRejectBlock) throws {
|
|
let jsonData = try toJsonData(response)
|
|
|
|
if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
|
|
resolve(jsonObject)
|
|
} else {
|
|
reject("ERROR", "Failed to serialize JSON", nil)
|
|
}
|
|
}
|
|
|
|
fileprivate func toJsonString<T>(_ input: T) -> String? where T: Encodable {
|
|
if let jsonData = try? toJsonData(input),
|
|
let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
return jsonString
|
|
}
|
|
return nil
|
|
}
|
|
|
|
fileprivate func toJsonData<T>(_ input: T) throws -> Data where T: Encodable {
|
|
let encoder = JSONEncoder()
|
|
encoder.keyEncodingStrategy = .convertToSnakeCase
|
|
return try encoder.encode(input)
|
|
}
|