Skip to content

Commit

Permalink
Fix - Expiry and signature validation
Browse files Browse the repository at this point in the history
  • Loading branch information
josmilan committed Jul 26, 2024
1 parent 3b7fdc5 commit 69ce1e4
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
import Foundation

protocol CredentialValidaorProtocol {
func validateCredential(jwt: String?) async throws
func validateCredential(jwt: String?, jwksURI: String?) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,96 +17,15 @@ public class CredentialValidatorService: CredentialValidaorProtocol {
public static var shared = CredentialValidatorService()
public init() {}

public func validateCredential(jwt: String?) async throws {
let isJWTExpired = validateExpiryDate(jwt: jwt) ?? false
let isSignatureExpied = await validateSign(jwt: jwt) ?? false
if !isJWTExpired {
public func validateCredential(jwt: String?, jwksURI: String?) async throws {
let isJWTExpired = ExpiryValidator.validateExpiryDate(jwt: jwt) ?? false
let isSignatureExpied = await SignatureValidator.validateSign(jwt: jwt, jwksURI: jwksURI) ?? false
if isJWTExpired {
throw ValidationError.JWTExpired
}
if !isSignatureExpied {
throw ValidationError.signatureExpired
}
}

public func validateExpiryDate(jwt: String?) -> Bool? {
guard let split = jwt?.split(separator: "."),
let jsonString = "\(split[1])".decodeBase64(),
let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false }
guard let vc = jsonObject["vc"] as? [String: Any], let expirationDate = vc["expirationDate"] as? String else { return true}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
guard let expiryDate = dateFormatter.date(from: expirationDate) else { return false}
let currentDate = Date()
if currentDate <= expiryDate {
return true
} else {
return false
}
}

public func validateSign(jwt: String?) async -> Bool? {
guard let split = jwt?.split(separator: "."),
let jsonString = "\(split[0])".decodeBase64(),
let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false }
guard let kid = jsonObject["kid"] as? String else { return true}
var jwk: [String: Any] = [:]
if kid.hasPrefix("did:key:z") {
jwk = processJWKfromKid(did: kid)
} else if kid.hasPrefix("did:ebsi:z") {
jwk = await processJWKforEBSI(did: kid)
} else {

}
return true
}


func processJWKfromKid(did: String?) -> [String: Any] {
do {
guard let did = did else { return [:]}
let components = did.split(separator: "#")
guard let didPart = components.first else {
return [:]
}
let multibaseString = String(didPart.dropFirst("did:key:z".count))

guard let decodedData = Base58.base58Decode(multibaseString) else {
print("Failed to decode Multibase string")
return [:]
}

let multicodecPrefixLength = 3
guard decodedData.count > multicodecPrefixLength else {
print("Invalid decoded data length")
return [:]
}
let jsonData = Data(decodedData.dropFirst(multicodecPrefixLength))

let jwk = try JSONSerialization.jsonObject(with: jsonData, options: [])
return jwk as? [String: Any] ?? [:]
} catch {
print("Error: \(error)")
return [:]
}
}

func processJWKforEBSI(did: String?) async -> [String: Any]{
guard let did = did else { return [:]}
let ebsiEndPoint = "https://api-conformance.ebsi.eu/did-registry/v5/identifiers/\(did)"
do {
guard let url = URL(string: ebsiEndPoint) else { return [:]}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:]}
guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let verificationMethods = jsonObject["verificationMethod"] as? [[String: Any]] else { return [:]}
for data in verificationMethods {
if let publicKeyJwk = data["publicKeyJwk"] as? [String: Any], let crv = publicKeyJwk["crv"] as? String, crv == "P-256" {
return publicKeyJwk
}
}
} catch {
print("error")
}
return [:]
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,22 @@
import Foundation

class ExpiryValidator {

static func validateExpiryDate(jwt: String?) -> Bool? {
guard let split = jwt?.split(separator: "."),
let jsonString = "\(split[1])".decodeBase64(),
let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false }
guard let vc = jsonObject["vc"] as? [String: Any], let expirationDate = vc["expirationDate"] as? String else { return false }
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
guard let expiryDate = dateFormatter.date(from: expirationDate) else { return false}
let currentDate = Date()
if currentDate <= expiryDate {
return false
} else {
return true
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,155 @@
//

import Foundation
import Base58Swift
import Security
import CryptoKit

class SignatureValidator {

static func validateSign(jwt: String?, jwksURI: String?) async -> Bool? {
var jwk: [String: Any] = [:]
guard let split = jwt?.split(separator: "."),
let jsonString = "\(split[0])".decodeBase64(),
let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false }
if let kid = jsonObject["kid"] as? String {
if kid.hasPrefix("did:key:z") {
jwk = processJWKfromKid(did: kid)
} else if kid.hasPrefix("did:ebsi:z") {
jwk = await processJWKforEBSI(did: kid)
} else {
jwk = await processJWKFromJwksURI2(kid: kid, jwksURI: jwksURI)
}
} else {
let kid = jsonObject["kid"] as? String
jwk = await processJWKFromJwksURI2(kid: kid, jwksURI: jwksURI)
}
return validateSignature(jwt: jwt, jwk: jwk)
}


static func processJWKfromKid(did: String?) -> [String: Any] {
guard let did = did else { return [:]}
let components = did.split(separator: "#")
guard let didPart = components.first else {
return [:]
}
return DidService.shared.createJWKfromDID(did: String(didPart))
}

static func processJWKforEBSI(did: String?) async -> [String: Any]{
guard let did = did else { return [:]}
let ebsiEndPoint = "https://api-conformance.ebsi.eu/did-registry/v5/identifiers/\(did)"
do {
guard let url = URL(string: ebsiEndPoint) else { return [:]}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:]}
guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let verificationMethods = jsonObject["verificationMethod"] as? [[String: Any]] else { return [:]}
for data in verificationMethods {
if let publicKeyJwk = data["publicKeyJwk"] as? [String: Any], let crv = publicKeyJwk["crv"] as? String, crv == "P-256" {
return publicKeyJwk
}
}
} catch {
print("error")
}
return [:]
}

static func processJWKFromJwksURI2(kid: String?, jwksURI: String?) async -> [String: Any] {
guard let jwksURI = jwksURI else {return [:]}
return await fetchJwkData(kid: kid, jwksUri: jwksURI)
}

static func fetchJwkData(kid: String?, jwksUri: String)async -> [String: Any] {
guard let url = URL(string: jwksUri) else {
return [:]
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:]}
guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let keys = jsonObject["keys"] as? [[String: Any]] else { return [:]}

var jwkKey: [String: Any]? = keys.first { $0["use"] as? String == "sig" }

if jwkKey == nil, let kid = kid {
jwkKey = keys.first { $0["kid"] as? String == kid }
}
return jwkKey ?? [:]

} catch {
print("error")
}
return [:]
}

static private func validateSignature(jwt: String?, jwk: [String: Any]) -> Bool? {
let segments = jwt?.split(separator: ".")
guard segments?.count == 3 else {
return true
}
let headerData = String(segments?[0] ?? "")
let payloadData = String(segments?[1] ?? "")
var sigatureData = String(segments?[2] ?? "")
if sigatureData.contains("~") {
let splitData = sigatureData.split(separator: "~")
sigatureData = String(splitData[0])
}
guard let headerEncoded = Data(base64URLEncoded: headerData) else { return false }
guard let signatureEncoded = Data(base64URLEncoded: sigatureData) else { return false }
guard let headerJson = try? JSONSerialization.jsonObject(with: headerEncoded, options: []) as? [String: Any], let alg = headerJson["alg"] as? String else {
return false
}
guard let crv = jwk["crv"] as? String else {
return false
}
let algToCrvMap: [String: String] = [
"ES256": "P-256",
"ES384": "P-384",
"ES512": "P-521"
]
if let expectedCrv = algToCrvMap[alg], expectedCrv != crv {
return false
}
guard let publicKey = extractPublicKey(from: jwk) else {
return false
}

let signedData = "\(headerData).\(payloadData)".data(using: .utf8)!
let isVerified = verifySignature(signature: signatureEncoded, for: signedData, using: publicKey)

return isVerified
}

static private func extractPublicKey(from jwk: [String: Any]) -> P256.Signing.PublicKey? {
guard let crv = jwk["crv"] as? String, crv == "P-256",
let x = jwk["x"] as? String,
let y = jwk["y"] as? String,
let xData = Data(base64URLEncoded: x),
let yData = Data(base64URLEncoded: y) else {
return nil
}

var publicKeyData = Data()
publicKeyData.append(0x04)
publicKeyData.append(xData)
publicKeyData.append(yData)

do {
let publicKey = try P256.Signing.PublicKey(x963Representation: publicKeyData)
return publicKey
} catch {
print("Error creating public key: \(error)")
return nil
}
}

static private func verifySignature(signature: Data, for data: Data, using publicKey: P256.Signing.PublicKey) -> Bool {
guard let ecdsaSignature = try? P256.Signing.ECDSASignature(rawRepresentation: signature) else {
print("Error converting signature to ECDSASignature")
return false
}
return publicKey.isValidSignature(ecdsaSignature, for: data)
}

}
25 changes: 25 additions & 0 deletions Sources/eudiWalletOidcIos/Service/DidService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,29 @@ public class DidService {
}
return nil
}

public func createJWKfromDID(did: String?) -> [String: Any] {
do {
guard let did = did else { return [:]}
let multibaseString = String(did.dropFirst("did:key:z".count))

guard let decodedData = Base58.base58Decode(multibaseString) else {
print("Failed to decode Multibase string")
return [:]
}

let multicodecPrefixLength = 3
guard decodedData.count > multicodecPrefixLength else {
print("Invalid decoded data length")
return [:]
}
let jsonData = Data(decodedData.dropFirst(multicodecPrefixLength))

let jwk = try JSONSerialization.jsonObject(with: jsonData, options: [])
return jwk as? [String: Any] ?? [:]
} catch {
print("Error: \(error)")
return [:]
}
}
}

0 comments on commit 69ce1e4

Please sign in to comment.