Files
bundesmessenger-ios/bwi/TokenVerification/TokenVerificator.swift
2025-02-11 14:49:54 +01:00

117 lines
4.2 KiB
Swift

//
/*
* Copyright (c) 2022 BWI GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import SwiftJWT
struct ServerTokenClaims: Claims {
let issuer: String
let sub: String
let exp: Date?
let iat: Date?
let jti: String
let version: Int
}
// verifies if a selected homeserver is valid to be used with bundesmessenger
struct ServerTokenVerificator {
// takes a token and the selected server url, returns true if token is valid for at least one public key in the subfolder publickeys. Additionally the token needs to be valid (inside the valid timestamp) and contain a matching homeserver
func verifyToken( baseURL: String, tokens: [String] ) -> Bool {
let publicKeys = publicKeys(folder: Bundle.main.resourcePath! + "/publickeys" )
let homeServerURL = baseURL.replacingOccurrences(of: "https://", with: "")
for key in publicKeys {
guard let keyData = key.data(using: .utf8) else {
continue
}
// only one public key needs to be fine
let jwtVerifier = JWTVerifier.ps512(publicKey: keyData)
do {
for token in tokens {
let verified = JWT<ServerTokenClaims>.verify(token, using: jwtVerifier)
let verifiedJWT = try JWT<ServerTokenClaims>(jwtString: token, verifier: jwtVerifier)
let validated = verifiedJWT.validateClaims()
let matchingHomeServer = verifiedJWT.claims.sub == homeServerURL
if verified && (validated == .success) && matchingHomeServer {
return true
}
}
} catch {
// counts like an unverified Token
}
}
// no public key was able to verify the token
return false
}
// fetch the current token from the new endpoint,
func fetchToken( baseURL: String ) async -> [String]? {
let path = "/_bum/client/v1/verify"
guard let url = URL(string: baseURL + path), let safeURL = url.ensureHTTPS() else {
return nil
}
do {
let config = URLSessionConfiguration.default
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
let session : URLSession = URLSession(configuration: config)
let (data, _) = try await session.data(from: safeURL)
let fetchedStrings = try JSONDecoder().decode([String].self, from: data)
return fetchedStrings
} catch (let error) {
if let error = error as? URLError, error.code == .notConnectedToInternet {
return nil
}
return [String]()
}
}
// reads the current public key folder into a string array of public keys
private func publicKeys(folder: String) -> [String] {
var keys: [String] = []
let fm = FileManager.default
do {
let items = try fm.contentsOfDirectory(atPath: folder)
for item in items {
let fileUrl = URL(fileURLWithPath: folder + "/" + item)
do {
let key = try String(contentsOf: fileUrl, encoding: .utf8)
keys.append(key)
} catch {
// invalid files do not count as public keys and are just not inserted into the array
}
}
} catch {
// should not happen but in this case there are no public keys == no verification
}
return keys
}
}