Skip to content

Commit

Permalink
fix(GiniMerchantSDK): Fix init issues, update ssl manager
Browse files Browse the repository at this point in the history
fix sonar issue, update ssl manager to check domain, update log level

# Conflicts:
#	MerchantSDK/GiniMerchantSDK/Sources/GiniMerchantSDK/Core/GiniMerchant.swift
  • Loading branch information
motokotomax committed Jul 24, 2024
1 parent 69a2773 commit 079645d
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,14 @@ public struct DataForReview {
- domain: The domain associated with your client credentials. This is used to scope the client credentials to a specific domain.
- pinningConfig: Configuration for certificate pinning. Format ["PinnedDomains" : ["PublicKeyHashes"]]
*/
public init(id: String, secret: String, domain: String, pinningConfig: [String: [String]]) {
let delegate = GiniSessionDelegate(pinnedKeyHashes: pinningConfig.values.flatMap { $0 })
public init(id: String,
secret: String,
domain: String,
pinningConfig: [String: [String]]) {
let client = Client(id: id, secret: secret, domain: domain)
self.giniApiLib = GiniHealthAPI.Builder(client: client, logLevel: .debug, sessionDelegate: delegate).build()
self.giniApiLib = GiniHealthAPI.Builder(client: client,
logLevel: .none,
sessionDelegate: GiniSessionDelegate(pinningConfig: pinningConfig)).build()
self.documentService = DefaultDocumentService(docService: giniApiLib.documentService())
self.paymentService = giniApiLib.paymentService()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation
class GiniSessionDelegate: NSObject, URLSessionDelegate {
private let pinningManager: SSLPinningManager

init(pinnedKeyHashes: [String]) {
self.pinningManager = SSLPinningManager(pinnedKeyHashes: pinnedKeyHashes)
internal init(pinningConfig: [String: [String]]) {
self.pinningManager = SSLPinningManager(pinningConfig: pinningConfig)
}

func urlSession(_ session: URLSession,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,28 @@ import Foundation
import CommonCrypto

struct SSLPinningManager {
// Custom error types for SSL pinning
private enum PinningError: Error {
case noCertificatesFromServer
case failedToGetPublicKey
case failedToGetDataFromPublicKey
case receivedWrongCertificate
}

// ASN.1 header for RSA 2048-bit keys. The same for all keys
private static let rsa2048ASN1Header: [UInt8] = [
0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00
]

private let pinnedKeyHashes: [String]

init(pinnedKeyHashes: [String]) {
self.pinnedKeyHashes = pinnedKeyHashes
}
// Dictionary mapping domain names to their expected public key hashes
private let pinningConfig: [String: [String]]

init(pinningConfig: [String: [String]]) {
self.pinningConfig = pinningConfig
}

// Function to validate the server's certificate
func validate(challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
do {
Expand All @@ -42,24 +46,39 @@ struct SSLPinningManager {
//MARK: - private methods

private extension SSLPinningManager {
// Validate the server's certificate and return the trust object if valid
func validateAndGetTrust(with challenge: URLAuthenticationChallenge) throws -> SecTrust {
// Step 1: Retrieve the server's trust object and its certificate chain
guard let trust = challenge.protectionSpace.serverTrust,
let trustCertificateChain = trustCopyCertificateChain(trust),
!trustCertificateChain.isEmpty else {
throw PinningError.noCertificatesFromServer
}

// Step 2: Get the domain from the challenge and check if it has a pinning configuration
guard let domain = challenge.protectionSpace.host.lowercased() as String? else {
throw PinningError.receivedWrongCertificate
}

// Step 3: Retrieve the pinned key hashes from pinning config for the domain
guard let pinnedKeyHashes = pinningConfig[domain] else {
throw PinningError.receivedWrongCertificate
}

// Step 4: Iterate over the server's certificates to find a matching public key hash
for serverCertificate in trustCertificateChain {
let publicKey = try getPublicKey(for: serverCertificate)
let publicKeyHash = try getKeyHash(of: publicKey)

// If a matching hash is found, the certificate is valid
if pinnedKeyHashes.contains(publicKeyHash) {
return trust
}
}
throw PinningError.receivedWrongCertificate
}

// Extract the public key from the server's certificate
func getPublicKey(for certificate: SecCertificate) throws -> SecKey {
let policy = SecPolicyCreateBasicX509()
var trust: SecTrust?
Expand All @@ -72,6 +91,7 @@ private extension SSLPinningManager {
return publicKey
}

// Generate a SHA-256 hash of the public key
func getKeyHash(of publicKey: SecKey) throws -> String {
guard let publicKeyCFData = SecKeyCopyExternalRepresentation(publicKey, nil) else {
throw PinningError.failedToGetDataFromPublicKey
Expand Down

0 comments on commit 079645d

Please sign in to comment.