Skip to content

Commit cab5656

Browse files
authored
Merge pull request #8 from dkoster95/feature/update-readme-file
Updated documentation
2 parents c3bc641 + 4d13aa0 commit cab5656

12 files changed

Lines changed: 584 additions & 0 deletions

Docs/CertificatePinning.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
## **iOS Security: Certificate Pinning**
2+
As developers we need our apps to: work properly (obviously), be easy to mantain, testable, scalable and **SECURE**.
3+
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.
4+
5+
I always recommend to follow OWASP and their Mobile Apps Top 10 security threats and the solutions for it
6+
(https://owasp.org/www-project-mobile-top-10/)
7+
8+
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).
9+
10+
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.
11+
12+
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.
13+
14+
For more information about TLS/SSL handshake and how it works check OWASP links.
15+
https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning
16+
17+
The module has 2 strategies to validate the certificate: Pin the certificate and pin the public key.
18+
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).
19+
Lets jump right to the Code:
20+
21+
```swift
22+
public protocol CertificatePinner {
23+
func isServerTrusted(challenge: URLAuthenticationChallenge) -> Bool
24+
}
25+
```
26+
27+
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.
28+
```swift
29+
public class QHCertificatePinner: CertificatePinner {
30+
private let pinningStrategies: [String: [PinningStrategy]]
31+
32+
public init(pinningStrategies: [String: [PinningStrategy]]) {
33+
self.pinningStrategies = pinningStrategies
34+
}
35+
....
36+
...
37+
```
38+
39+
Here is the QuickHatch implementation: you initialize your pinner with a dictionary of hosts (string) with pinning strategies for those hosts.
40+
41+
The pinning strategy protocol:
42+
```swift
43+
public protocol PinningStrategy {
44+
func validate(serverTrust: SecTrust) -> Bool
45+
}
46+
```
47+
48+
CertificatePinning strategy:
49+
```swift
50+
public class CertificatePinningStrategy: PinningStrategy {
51+
private let certificates: [SecCertificate]
52+
53+
public init(certificates: [SecCertificate])
54+
```
55+
its initialized with an array of Certificates (included in your bundle) and the validate function will use those certificates.
56+
57+
The public key strategy:
58+
```swift
59+
public class PublicKeyPinningStrategy: PinningStrategy {
60+
private let publicKeys: [String]
61+
private let hasher: Hasher
62+
63+
public init(publicKeys: [String], hasher: Hasher = CCSHA256Hasher.shared)
64+
```
65+
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.
66+
67+
68+
Real Example:
69+
```swift
70+
71+
let pinner = QHCertificatePinner(pinningStrategies: ["quickhatch.com": [CertificatePinningStrategy(certificates: [certificate],
72+
PublicKeyPinningStrategy(publicKeys: ["your public key has"],
73+
hasher: youHasher))
74+
]
75+
])
76+
```
77+
78+
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.
79+
80+
By default if the host doesnt have a pinning strategy set it will return as trusted.
81+
82+
All of this look great and fancy but where do we use our pinner ???
83+
84+
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.
85+
```swift
86+
public class URLSessionPinningDelegate: NSObject, URLSessionDelegate {
87+
private let certificatePinner: CertificatePinner
88+
89+
public init(certificatePinner: CertificatePinner) {
90+
self.certificatePinner = certificatePinner
91+
}
92+
93+
public func urlSession(_ session: URLSession,
94+
didReceive challenge: URLAuthenticationChallenge,
95+
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
96+
guard let serverTrust = challenge.protectionSpace.serverTrust else {
97+
completionHandler(.cancelAuthenticationChallenge, nil)
98+
return
99+
}
100+
if certificatePinner.isServerTrusted(challenge: challenge) {
101+
completionHandler(.useCredential, URLCredential(trust: serverTrust))
102+
return
103+
}
104+
completionHandler(.cancelAuthenticationChallenge, nil)
105+
}
106+
```

Docs/CodableExtensions.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## **Using Codable extension**
2+
3+
Codable (Encodable & Decodable) is a protocol launched by Apple to encode and decode JSONs very easily,
4+
QuickHatch provides an extension for mapping responses to an object or an array.
5+
6+
This is a sample for a **response** mapping using QuickHatchHTTP:
7+
8+
```swift
9+
struct User: Codable {
10+
var name: String
11+
var age: Int
12+
}
13+
14+
let request = networkRequestFactory.response(request: yourRequest) { (result: Result<Response<User>, Error>) in
15+
switch result {
16+
case .success(let response):
17+
//do something
18+
case .failure(let error):
19+
//handle error
20+
}
21+
}
22+
request.resume()
23+
```
24+
25+
26+
---

Docs/CombineSupport.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## **Using Combine extension**
2+
3+
The network Request Factory also has support for swift Combine if you decide to go full Reactive programming.
4+
5+
```swift
6+
func response<CodableData: Codable>(urlRequest: URLRequest,
7+
jsonDecoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<Response<CodableData>, Error>
8+
```
9+
This is a sample for a **response** mapping using QuickHatch Combine function:
10+
11+
```swift
12+
struct User: Codable {
13+
var name: String
14+
var age: Int
15+
}
16+
var subscriptions: Set<AnyCancellable> = []
17+
18+
let request = networkRequestFactory.response(request: yourRequest)
19+
.sink(receiveCompletion: { receiveCompletion in
20+
21+
},
22+
receiveValue: { (value: User) in
23+
}).store(in: &subscriptions)
24+
```
25+
26+
27+
---

Docs/Error.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## **Errors**
2+
- If an error is returned in a network request QuickHatchHTTP provides an enum with errors
3+
```swift
4+
public enum RequestError: Error, Equatable {
5+
case unauthorized
6+
case unknownError(statusCode: Int)
7+
case cancelled
8+
case noResponse
9+
case requestWithError(statusCode: HTTPStatusCode)
10+
case serializationError(error: Error)
11+
case invalidParameters
12+
case malformedRequest
13+
case other(error: Error)
14+
}
15+
```
16+
Now if we want to check what error is..
17+
```swift
18+
import QuickHatch
19+
20+
class LoginViewPresenter {
21+
private let networkRequestFactory: NetworkRequestFactory
22+
private weak var view: LoginView
23+
24+
init(requestFactory: NetworkRequestFactory, view: LoginView) {
25+
self.networkRequestFactory = requestFactory
26+
self.view = view
27+
}
28+
29+
func login(user: String, password: String) {
30+
let request = URLRequest("getimage.com/user=\(user)&password=\(password)")
31+
networkRequestFactory.data(urlRequest: request) { result in
32+
switch result {
33+
case .success(let response):
34+
// show error messsage
35+
view?.showSuccessMessage(response.data)
36+
case .failure(let error):
37+
//show message or nothing
38+
if let requestError = error as? RequestError,
39+
requestError == .unauthorized {
40+
//show auth error
41+
}
42+
view?.showErrorMessage("Error downloading profile image")
43+
}
44+
}
45+
}
46+
```
47+
In this sample we are checking whether the error is unauthorized type or not.

Docs/GettingStarted.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
## **Getting Started**
2+
3+
- When you develop an app usually you have to fetch data from server/s.
4+
- due to this you will want to use a Networking framework but you want to keep the code clean, independent and mantainable.
5+
- You may want to use libraries like Alamofire, Malibu or your own implementation using URLSession or URLConnection.
6+
- 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.
7+
- 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.
8+
9+
![](https://github.com/dkoster95/QuickHatchHTTP/blob/main/diagram.png)
10+
11+
The name of this networkLayer interface in QuickHatchHTTP is NetworkRequestFactory :).
12+
```swift
13+
14+
public protocol NetworkRequestFactory {
15+
func data(request: URLRequest,
16+
dispatchQueue: DispatchQueue,
17+
completionHandler completion: @Sendable @escaping (Result<HTTPResponse, Error>) -> Void) -> DataTask
18+
func dataPublisher(request: URLRequest) -> AnyPublisher<HTTPResponse,Error>
19+
func data(request: URLRequest) async throws -> HTTPResponse
20+
}
21+
```
22+
23+
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
24+
25+
Now lets say your app is using a MVP (Model-View-Presenter) pattern and you want to use QuickHatch to map your networking layer.
26+
Lets show a sample of how that would be:
27+
28+
First we have our presenter, class in charge of connecting View and Models.
29+
Keep in mind these its just a sample, you should always use clean code principles such as SOLID or KISS.
30+
31+
32+
```swift
33+
class ViewPresenter: Presenter {
34+
private var requestFactory: NetworkRequestFactory
35+
var view: View?
36+
37+
init(requestFactory: NetworkRequestFactory) {
38+
self.requestFactory = requestFactory
39+
}
40+
41+
func fetchData(username: String) {
42+
view?.showLoading()
43+
// Here you initialize the URLRequest object, for now we will use a quickHatch get request
44+
let yourRequest = URLRequest.get(url: URL("www.google.com",
45+
parameters: ["username": username],
46+
parameterEncoding: URLEncoding.queryString)
47+
requestFactory.data(request: yourRequest, dispatch: .main) { result in
48+
view?.stopLoading()
49+
switch result {
50+
case .success(let data):
51+
view?.showSuccessAlert(data)
52+
case .failure(let error):
53+
view?.showErrorAlert(error)
54+
}
55+
56+
}.resume()
57+
}
58+
}
59+
```
60+
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.
61+
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,
62+
but with QuickHatch you are attaching yourself to a protocol, and the implementation can change anytime.
63+
64+
And now we have the code of our view and the dependency injector:
65+
66+
```swift
67+
struct DependencyInjector {
68+
func initializeSampleView() {
69+
let presenter = ViewPresenter(requestFactory: QHRequestFactory(urlSession: URLSession.shared)
70+
let sampleView = SampleView(presenter: presenter)
71+
application.rootView = sampleView
72+
}
73+
}
74+
```
75+
76+
```swift
77+
class SampleView: View {
78+
79+
private var presenter: ViewPresenter
80+
private var textField: TextField
81+
82+
init(presenter: Presenter) {
83+
self.presenter = presenter
84+
self.presenter.view = self
85+
}
86+
87+
@IBAction buttonTapped() {
88+
presenter.fetchData(textField.text)
89+
}
90+
91+
func stopLoading() {
92+
// stop spinner
93+
}
94+
95+
func showLoading() {
96+
// start spinner
97+
}
98+
99+
func showSuccessAlert(data: Data) {
100+
// show alert for success case
101+
}
102+
103+
func showErrorAlert(error: Error) {
104+
// show alert for error case
105+
}
106+
}
107+
```
108+
109+
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,
110+
in this case we used the NetworkFactory implementation that QuickHatch provides, this one is initizalized with a URLSession object.
111+
If we want to use an Alamofire Implementation:
112+
you go to the dependency injector and set the new implementation.
113+
114+
```swift
115+
let presenter = ViewPresenter(requestFactory: AlamofireRequestFactory())
116+
```
117+
118+
---

Docs/JSONEncoding.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## **JSON Parameter Encoding**
2+
- Another important feature of the framework is the **parameter encoding** for a URLRequest
3+
4+
5+
Here you see the parameter encoding protocol, simple right ?
6+
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.
7+
8+
```swift
9+
public protocol ParameterEncoding {
10+
func encode(_ urlRequest: URLRequestProtocol, with parameters: Parameters?) throws -> URLRequest
11+
}
12+
```
13+
14+
The JSON encoding will escape and encode the parameters in the body.
15+
16+
It will look like this
17+
params: name and password
18+
```swift
19+
{
20+
"name": "quickhatch",
21+
"password": "1234"
22+
}
23+
```
24+
25+
Real Sample:
26+
```swift
27+
let getUsers = URLRequest.post(url: URL("getusers.com"),
28+
params: ["page":1],
29+
encoding: JSONEncoding.default,
30+
headers: ["Authorization": "token 1232"])
31+
```

0 commit comments

Comments
 (0)