Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions Docs/CertificatePinning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
## **iOS Security: Certificate Pinning**
As developers we need our apps to: work properly (obviously), be easy to mantain, testable, scalable and **SECURE**.
Users will trust their confidential data to our apps and we need to handle that carefully and prevent leaks or flaws that can cause data loss or exposure.

I always recommend to follow OWASP and their Mobile Apps Top 10 security threats and the solutions for it
(https://owasp.org/www-project-mobile-top-10/)

The threat that is treated by QuickHatch is Top 3 Insecure Communication (https://owasp.org/www-project-mobile-top-10/2016-risks/m3-insecure-communication).

The Certificate Pinning Module is made to prevent Man in the middle attacks (you can find more detailed information in the OWASP links) but basically what we are preventing is that an attacker is able to see all your network traffic.

Certificate Pinning works on top of HTTPS requests and what is gonna do is to verify the authenticity of the SSL Certificate used by the server and the connection between your device and server.

For more information about TLS/SSL handshake and how it works check OWASP links.
https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning

The module has 2 strategies to validate the certificate: Pin the certificate and pin the public key.
which one to choose ? Its up to you, if you choose to pin the certificate you will have to update the app bundle every time the certificate is updated, however if you pin the public key you wont have this problem because the public key is not going to change, but you may violate the key rotation standard (its always good to change the keys of your app).
Lets jump right to the Code:

```swift
public protocol CertificatePinner {
func isServerTrusted(challenge: URLAuthenticationChallenge) -> Bool
}
```

First of all we have a protocol CertificatePinner with only one function that is to check wether the server we are receiving is trusted by our app or not. **URLAuthenticationChallenge** is the swift class that contains information about the server certficate.
```swift
public class QHCertificatePinner: CertificatePinner {
private let pinningStrategies: [String: [PinningStrategy]]

public init(pinningStrategies: [String: [PinningStrategy]]) {
self.pinningStrategies = pinningStrategies
}
....
...
```

Here is the QuickHatch implementation: you initialize your pinner with a dictionary of hosts (string) with pinning strategies for those hosts.

The pinning strategy protocol:
```swift
public protocol PinningStrategy {
func validate(serverTrust: SecTrust) -> Bool
}
```

CertificatePinning strategy:
```swift
public class CertificatePinningStrategy: PinningStrategy {
private let certificates: [SecCertificate]

public init(certificates: [SecCertificate])
```
its initialized with an array of Certificates (included in your bundle) and the validate function will use those certificates.

The public key strategy:
```swift
public class PublicKeyPinningStrategy: PinningStrategy {
private let publicKeys: [String]
private let hasher: Hasher

public init(publicKeys: [String], hasher: Hasher = CCSHA256Hasher.shared)
```
This implementation is initialized with an array of public keys (hash) and with a hasher class that is gonna apply the hashing to the public key coming in the **URLAuthenticationChallenge** and is gonna compare 2 strings to validate.


Real Example:
```swift

let pinner = QHCertificatePinner(pinningStrategies: ["quickhatch.com": [CertificatePinningStrategy(certificates: [certificate],
PublicKeyPinningStrategy(publicKeys: ["your public key has"],
hasher: youHasher))
]
])
```

In this case we set 2 pinning strategies for **quickhatch.com** one public key pinning with one public key and your own hasher implementation and one Certificate pinning strategy with one certificate.

By default if the host doesnt have a pinning strategy set it will return as trusted.

All of this look great and fancy but where do we use our pinner ???

The answer is URLSession method **urlSession(_ session: URLSession,didReceive challenge: URLAuthenticationChallenge,completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)** so There is a class implementing URLSessionDelegate, you can use this class to initialize your urlsession or you can subclass it you choice.
```swift
public class URLSessionPinningDelegate: NSObject, URLSessionDelegate {
private let certificatePinner: CertificatePinner

public init(certificatePinner: CertificatePinner) {
self.certificatePinner = certificatePinner
}

public func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
if certificatePinner.isServerTrusted(challenge: challenge) {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
return
}
completionHandler(.cancelAuthenticationChallenge, nil)
}
```
26 changes: 26 additions & 0 deletions Docs/CodableExtensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## **Using Codable extension**

Codable (Encodable & Decodable) is a protocol launched by Apple to encode and decode JSONs very easily,
QuickHatch provides an extension for mapping responses to an object or an array.

This is a sample for a **response** mapping using QuickHatchHTTP:

```swift
struct User: Codable {
var name: String
var age: Int
}

let request = networkRequestFactory.response(request: yourRequest) { (result: Result<Response<User>, Error>) in
switch result {
case .success(let response):
//do something
case .failure(let error):
//handle error
}
}
request.resume()
```


---
27 changes: 27 additions & 0 deletions Docs/CombineSupport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## **Using Combine extension**

The network Request Factory also has support for swift Combine if you decide to go full Reactive programming.

```swift
func response<CodableData: Codable>(urlRequest: URLRequest,
jsonDecoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<Response<CodableData>, Error>
```
This is a sample for a **response** mapping using QuickHatch Combine function:

```swift
struct User: Codable {
var name: String
var age: Int
}
var subscriptions: Set<AnyCancellable> = []

let request = networkRequestFactory.response(request: yourRequest)
.sink(receiveCompletion: { receiveCompletion in

},
receiveValue: { (value: User) in
}).store(in: &subscriptions)
```


---
47 changes: 47 additions & 0 deletions Docs/Error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## **Errors**
- If an error is returned in a network request QuickHatchHTTP provides an enum with errors
```swift
public enum RequestError: Error, Equatable {
case unauthorized
case unknownError(statusCode: Int)
case cancelled
case noResponse
case requestWithError(statusCode: HTTPStatusCode)
case serializationError(error: Error)
case invalidParameters
case malformedRequest
case other(error: Error)
}
```
Now if we want to check what error is..
```swift
import QuickHatch

class LoginViewPresenter {
private let networkRequestFactory: NetworkRequestFactory
private weak var view: LoginView

init(requestFactory: NetworkRequestFactory, view: LoginView) {
self.networkRequestFactory = requestFactory
self.view = view
}

func login(user: String, password: String) {
let request = URLRequest("getimage.com/user=\(user)&password=\(password)")
networkRequestFactory.data(urlRequest: request) { result in
switch result {
case .success(let response):
// show error messsage
view?.showSuccessMessage(response.data)
case .failure(let error):
//show message or nothing
if let requestError = error as? RequestError,
requestError == .unauthorized {
//show auth error
}
view?.showErrorMessage("Error downloading profile image")
}
}
}
```
In this sample we are checking whether the error is unauthorized type or not.
118 changes: 118 additions & 0 deletions Docs/GettingStarted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
## **Getting Started**

- When you develop an app usually you have to fetch data from server/s.
- due to this you will want to use a Networking framework but you want to keep the code clean, independent and mantainable.
- You may want to use libraries like Alamofire, Malibu or your own implementation using URLSession or URLConnection.
- What QuickHatch provides is a set of protocols that allows you to follow clean code standards, SOLID principles and allows you to be flexible when it comes to Networking.
- First of all, your app will use a NetworkClient (this can be Alamofire, etc), and QuickHatch provides a protocol called NetworkRequestFactory that sets what a network client should respond when making a request.

![](https://github.com/dkoster95/QuickHatchHTTP/blob/main/diagram.png)

The name of this networkLayer interface in QuickHatchHTTP is NetworkRequestFactory :).
```swift

public protocol NetworkRequestFactory {
func data(request: URLRequest,
dispatchQueue: DispatchQueue,
completionHandler completion: @Sendable @escaping (Result<HTTPResponse, Error>) -> Void) -> DataTask
func dataPublisher(request: URLRequest) -> AnyPublisher<HTTPResponse,Error>
func data(request: URLRequest) async throws -> HTTPResponse
}
```

The very base protocol of the RequestFactory provides 2 functions to get Data out of a network request, one is using a ***completionHandler*** with a ***Request*** type return and the other one is using ***Combine's AnyPublisher*** if you choose to use Reactive Programming

Now lets say your app is using a MVP (Model-View-Presenter) pattern and you want to use QuickHatch to map your networking layer.
Lets show a sample of how that would be:

First we have our presenter, class in charge of connecting View and Models.
Keep in mind these its just a sample, you should always use clean code principles such as SOLID or KISS.


```swift
class ViewPresenter: Presenter {
private var requestFactory: NetworkRequestFactory
var view: View?

init(requestFactory: NetworkRequestFactory) {
self.requestFactory = requestFactory
}

func fetchData(username: String) {
view?.showLoading()
// Here you initialize the URLRequest object, for now we will use a quickHatch get request
let yourRequest = URLRequest.get(url: URL("www.google.com",
parameters: ["username": username],
parameterEncoding: URLEncoding.queryString)
requestFactory.data(request: yourRequest, dispatch: .main) { result in
view?.stopLoading()
switch result {
case .success(let data):
view?.showSuccessAlert(data)
case .failure(let error):
view?.showErrorAlert(error)
}

}.resume()
}
}
```
Here we have a View type (protocol), a fetchData method that is going to use the NetworkFactory to get the data from somewhere using a data response and we have the initializer where we are injecting the networkFactory.
Now previously if we used some NetworkClient framework here we would be using the implementation class instead of the protocol and we would be attached to that framework,
but with QuickHatch you are attaching yourself to a protocol, and the implementation can change anytime.

And now we have the code of our view and the dependency injector:

```swift
struct DependencyInjector {
func initializeSampleView() {
let presenter = ViewPresenter(requestFactory: QHRequestFactory(urlSession: URLSession.shared)
let sampleView = SampleView(presenter: presenter)
application.rootView = sampleView
}
}
```

```swift
class SampleView: View {

private var presenter: ViewPresenter
private var textField: TextField

init(presenter: Presenter) {
self.presenter = presenter
self.presenter.view = self
}

@IBAction buttonTapped() {
presenter.fetchData(textField.text)
}

func stopLoading() {
// stop spinner
}

func showLoading() {
// start spinner
}

func showSuccessAlert(data: Data) {
// show alert for success case
}

func showErrorAlert(error: Error) {
// show alert for error case
}
}
```

Great! now we have a presenter that uses an abstract networking layer, and we can switch the implementation by changing the code in the dependencyInjector,
in this case we used the NetworkFactory implementation that QuickHatch provides, this one is initizalized with a URLSession object.
If we want to use an Alamofire Implementation:
you go to the dependency injector and set the new implementation.

```swift
let presenter = ViewPresenter(requestFactory: AlamofireRequestFactory())
```

---
31 changes: 31 additions & 0 deletions Docs/JSONEncoding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## **JSON Parameter Encoding**
- Another important feature of the framework is the **parameter encoding** for a URLRequest


Here you see the parameter encoding protocol, simple right ?
the framework provides 3 implementations for this protocol URL, JSON and String encodings but you can create your own implementation and then inject it into the URLRequest initializer.

```swift
public protocol ParameterEncoding {
func encode(_ urlRequest: URLRequestProtocol, with parameters: Parameters?) throws -> URLRequest
}
```

The JSON encoding will escape and encode the parameters in the body.

It will look like this
params: name and password
```swift
{
"name": "quickhatch",
"password": "1234"
}
```

Real Sample:
```swift
let getUsers = URLRequest.post(url: URL("getusers.com"),
params: ["page":1],
encoding: JSONEncoding.default,
headers: ["Authorization": "token 1232"])
```
Loading