Skip to content
This repository has been archived by the owner on May 22, 2023. It is now read-only.

Commit

Permalink
Client app web api changed to Combine. Improved docs. General design …
Browse files Browse the repository at this point in the history
…pattern improvements
  • Loading branch information
rsantosbliss committed Jan 16, 2021
1 parent a57319a commit 44ae5de
Show file tree
Hide file tree
Showing 19 changed files with 481 additions and 217 deletions.
131 changes: 79 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

## About

Utilities arround [Apple CryptoKit](https://developer.apple.com/documentation/cryptokit)
Utilities and sample [__client app__](https://github.com/ricardopsantos/RJSP_Security/tree/master/_RJSecuritySampleClient) and [__web server__](https://github.com/ricardopsantos/RJSP_Security/tree/master/_RJSecuritySampleServer) around [__Apple CryptoKit__](https://developer.apple.com/documentation/cryptokit) concepts.

## Sample Usage

Expand All @@ -39,7 +39,7 @@ static let secretPlain = "my secret"
static let secretPlainData = CryptoKit.humanFriendlyPlainMessageToDataPlainMessage(secretPlain)!
```

### Sample Usage 1 : Using symmetric keys
### Sample Usage 1 : Encrypt and decrypt using symmetric keys

```swift
// Sender: Generating symmetric key and encrpting data USING shared symmetric key
Expand All @@ -52,7 +52,7 @@ let decryptedData = CryptoKit.decrypt(encryptedData: encryptedData, using

```

### Sample Usage 2 : Using public and private keys
### Sample Usage 2 : Encrypt and decrypt using public and private keys


```swift
Expand All @@ -71,84 +71,111 @@ let decryptedData = CryptoKit.decrypt(encryptedData: encryptedData,

## Sample working projects

Inside the folders __RJSecuritySampleClient__ and __RJSecuritySampleServer__ can be found sample client app (Swift) and sample server web app (Vapor Swift) ready to use.
Inside the folders [__RJSecuritySampleClient__]((https://github.com/ricardopsantos/RJSP_Security/tree/master/_RJSecuritySampleClient)) and [__RJSecuritySampleServer__](https://github.com/ricardopsantos/RJSP_Security/tree/master/_RJSecuritySampleServer) can be found sample client app (Swift) and sample server web app (Vapor Swift) ready to use.

Booth the client app and server use RJSP_Security lib installed via SPM and are a live working example of the key exchange process, and then a secure comunication.

Open both projects on Xcode, start the server (first), and then start the app.
Both the client app and server use RJSP_Security lib installed via SPM and are a live working example of the key exchange process, follewd by a secure comunication.

![alt text](_Documents/image1.png)

The example flow is as follows:
Open both projects on Xcode

* Start the server (first
* Start the app.

The projects samples flow is as follows:

__Step 1 :__ The app (client) send is public key to the server (on the request body). It also sends his userID (on the request header).

```swift
static func session(publicKey: Curve25519.KeyAgreement.PublicKey, userID: String) -> Request {
static func session(publicKey: Curve25519.KeyAgreement.PublicKey, userID: String) -> RequestModel {
let httpBody = [
"publicKey": CryptoKit.base64String(with: publicKey),
"userId": userID
]

return Request(route: "session",
httpMethod: "post",
httpBody: httpBody,
userId: userID)
return RequestModel(path: "session",
httpMethod: .post,
httpBody: httpBody,
userId: userID)
}
```

```swift
let sessionPublisher = webAPI.session(publicKey: privateKey.publicKey, userID: userID)
```

__Step 2:__ The server store the userID and the user public key (for future secure comunication) and returns to the client app the server public key.

```swift
app.post("session") { req -> String in
guard let userId = req.headerValue("USER_ID") else {
// User id must be sent on the request header
throw Abort(.badRequest)
}

// Store user public key
let sessionModel = try req.content.decode(SessionModel.self)
CryptoKit.PublicKeysHotStorage.store(publicKey: sessionModel.publicKey, for: userId)

// Return the web server public key
return privateKey.publicKey.toBase64String
app.post(Session.path) { req -> Session.ResponseModel in
guard let userId = req.headerValue("USER_ID") else {
// User id must be sent on the request header
throw Abort(.badRequest)
}

// Store user public key
let requestModel = try req.content.decode(Session.RequestModel.self)

CryptoKit.PublicKeysHotStorage.store(publicKey: requestModel.publicKey, for: userId)

// Return the web server public key
return Session.ResponseModel(publicKey: privateKey.publicKey.toBase64String)
}
```

__Step 3:__ The client app receives the server public key, and then with is (client) private key do a secure/encripted request to the server.

```swift
static func secure(encrypted: Data, userID: String) -> Request {
static func secure(encrypted: Data, userID: String) -> RequestModel {
let httpBody = ["secret": CryptoKit.encodeToSendOverNetwork(encrypted: encrypted)]
return Request(route: "secureRequest",
httpMethod: "post",
httpBody: httpBody,
userId: userID)
return RequestModel(path: "secureRequest",
httpMethod: .post,
httpBody: httpBody,
userId: userID)
}
```

__Step 4:__ The server receives the encripted request, and decript it using the client public key (stored on setep 1) and his (server) private key. After decripting the message, the server just return it as a "proof" of sucess.

```swift
app.post("secureRequest") { req -> String in
guard let userId = req.headerValue("USER_ID"),
let clientPublicKey = CryptoKit.PublicKeysHotStorage.get(for: userId) else {
// User id must be sent on header and
// the server must know the user public key allready
throw Abort(.badRequest)
}
let secureRequestBody = try req.content.decode(SecureRequestModel.self)

let encryptedData = secureRequestBody.secretData

guard let decryptData = CryptoKit.decrypt(encryptedData: encryptedData!,
receiver: privateKey,
sender: clientPublicKey,
salt: sharedSalt!) else {
throw Abort(.notAcceptable)
}
let humanFriendlyPlainMessage = CryptoKit.dataPlainMessageToHumanFriendlyPlainMessage(decryptData)

return "Your secret was [\(humanFriendlyPlainMessage ?? "")]"
// Session response with server public key...
let serverPublicKeyPublisher = sessionPublisher.compactMap { $0.publicKey }

// Secure request...
let secureRequestPublisher = serverPublicKeyPublisher.flatMap { (publicKey) -> AnyPublisher<ResponseDto.SecureRequest, APIError> in
let plainHumanMessage = "Hi server. Can you uppercase me? \(Date())"
let serverPublicKey = CryptoKit.publicKey(with: publicKey)!
let plainDataMessage = CryptoKit.humanFriendlyPlainMessageToDataPlainMessage(plainHumanMessage)!
let encryptedMessage = CryptoKit.encrypt(data: plainDataMessage,
sender: privateKey,
receiver: serverPublicKey,
salt: sharedSalt)
return webAPI.secure(encrypted: encryptedMessage!, userID: userID)
}
```

__Step 4:__ The server receives the encripted request, and decript it using the client public key (stored on step 1) and his (server) private key. After decripting the message, the server just return it as a "proof" of sucess.

```swift
app.post(SecureRequest.path) { req -> SecureRequest.ResponseModel in
guard let userId = req.headerValue("USER_ID"),
let clientPublicKey = CryptoKit.PublicKeysHotStorage.get(for: userId) else {
// User id must be sent on header and
// the server must know the user public key allready
throw Abort(.badRequest)
}
let requestModel = try req.content.decode(SecureRequest.RequestModel.self)

let encryptedData = requestModel.secretData

guard let decryptData = CryptoKit.decrypt(encryptedData: encryptedData!,
receiver: privateKey,
sender: clientPublicKey,
salt: sharedSalt!) else {
throw Abort(.notAcceptable)
}

// Decode the secret message
let humanFriendlyPlainMessage = CryptoKit.dataPlainMessageToHumanFriendlyPlainMessage(decryptData)!
let message = "Sure!\n\n\(humanFriendlyPlainMessage.uppercased())"
return SecureRequest.ResponseModel(message: message)
}
```
Binary file modified _Documents/image1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,21 @@
D73FB5AC25B03031004F63EE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D73FB5AA25B03031004F63EE /* Main.storyboard */; };
D73FB5AE25B03032004F63EE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D73FB5AD25B03032004F63EE /* Assets.xcassets */; };
D73FB5B125B03032004F63EE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D73FB5AF25B03032004F63EE /* LaunchScreen.storyboard */; };
D75CE80525B0666400CD6AFD /* WebAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75CE80425B0666400CD6AFD /* WebAPI.swift */; };
D75D0D3325B3596F001BC024 /* CombineSimpleNetworkAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75D0D3225B3596F001BC024 /* CombineSimpleNetworkAgent.swift */; };
D75D0D3625B35A22001BC024 /* WebAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75D0D3525B35A22001BC024 /* WebAPI.swift */; };
D75D0D4225B37B71001BC024 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75D0D4125B37B71001BC024 /* String+Extensions.swift */; };
D75D0D4825B37BA4001BC024 /* Combine+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75D0D4625B37BA4001BC024 /* Combine+Extensions.swift */; };
D75D0D4925B37BA4001BC024 /* Request+URLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75D0D4725B37BA4001BC024 /* Request+URLRequest.swift */; };
D75D0D5225B37E97001BC024 /* WebAPI+Requests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75D0D5125B37E97001BC024 /* WebAPI+Requests.swift */; };
D777521E25B230540021ADFF /* RJSLibUFNetworking in Frameworks */ = {isa = PBXBuildFile; productRef = D777521D25B230540021ADFF /* RJSLibUFNetworking */; };
D777522025B230540021ADFF /* RJSLibUFBase in Frameworks */ = {isa = PBXBuildFile; productRef = D777521F25B230540021ADFF /* RJSLibUFBase */; };
D777522225B230540021ADFF /* RJSLibUFALayouts in Frameworks */ = {isa = PBXBuildFile; productRef = D777522125B230540021ADFF /* RJSLibUFALayouts */; };
D777522425B230540021ADFF /* RJSLibUFAppThemes in Frameworks */ = {isa = PBXBuildFile; productRef = D777522325B230540021ADFF /* RJSLibUFAppThemes */; };
D777522625B230540021ADFF /* RJSLibUFStorage in Frameworks */ = {isa = PBXBuildFile; productRef = D777522525B230540021ADFF /* RJSLibUFStorage */; };
D7D362EE25B06D710036EB48 /* UserSessionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D362ED25B06D710036EB48 /* UserSessionModel.swift */; };
D7D362F325B06DA70036EB48 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D362F225B06DA70036EB48 /* Request.swift */; };
D7D362EE25B06D710036EB48 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D362ED25B06D710036EB48 /* Session.swift */; };
D7D362F325B06DA70036EB48 /* RequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D362F225B06DA70036EB48 /* RequestModel.swift */; };
D7D362FD25B06FB30036EB48 /* RJSecurity in Frameworks */ = {isa = PBXBuildFile; productRef = D7D362FC25B06FB30036EB48 /* RJSecurity */; };
D7D3630025B070330036EB48 /* WebAPI+Requests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D362FF25B070330036EB48 /* WebAPI+Requests.swift */; };
D7D3630025B070330036EB48 /* RequestsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D362FF25B070330036EB48 /* RequestsBuilder.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -34,10 +39,15 @@
D73FB5AD25B03032004F63EE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
D73FB5B025B03032004F63EE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
D73FB5B225B03032004F63EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D75CE80425B0666400CD6AFD /* WebAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebAPI.swift; sourceTree = "<group>"; };
D7D362ED25B06D710036EB48 /* UserSessionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionModel.swift; sourceTree = "<group>"; };
D7D362F225B06DA70036EB48 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
D7D362FF25B070330036EB48 /* WebAPI+Requests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WebAPI+Requests.swift"; sourceTree = "<group>"; };
D75D0D3225B3596F001BC024 /* CombineSimpleNetworkAgent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineSimpleNetworkAgent.swift; sourceTree = "<group>"; };
D75D0D3525B35A22001BC024 /* WebAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebAPI.swift; sourceTree = "<group>"; };
D75D0D4125B37B71001BC024 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
D75D0D4625B37BA4001BC024 /* Combine+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Combine+Extensions.swift"; sourceTree = "<group>"; };
D75D0D4725B37BA4001BC024 /* Request+URLRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Request+URLRequest.swift"; sourceTree = "<group>"; };
D75D0D5125B37E97001BC024 /* WebAPI+Requests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WebAPI+Requests.swift"; sourceTree = "<group>"; };
D7D362ED25B06D710036EB48 /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = "<group>"; };
D7D362F225B06DA70036EB48 /* RequestModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestModel.swift; sourceTree = "<group>"; };
D7D362FF25B070330036EB48 /* RequestsBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestsBuilder.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -76,6 +86,8 @@
D73FB5A325B03031004F63EE /* RJSSecuritySampleClient */ = {
isa = PBXGroup;
children = (
D75D0D4D25B37BCD001BC024 /* CombineSimpleNetworkAgent */,
D75D0D4025B37B64001BC024 /* Extensions */,
D7D362EC25B06D590036EB48 /* WebAPI */,
D7D362EB25B06D530036EB48 /* Models */,
D73FB5A425B03031004F63EE /* AppDelegate.swift */,
Expand All @@ -89,20 +101,47 @@
path = RJSSecuritySampleClient;
sourceTree = "<group>";
};
D75D0D4025B37B64001BC024 /* Extensions */ = {
isa = PBXGroup;
children = (
D75D0D4625B37BA4001BC024 /* Combine+Extensions.swift */,
D75D0D4725B37BA4001BC024 /* Request+URLRequest.swift */,
D75D0D4125B37B71001BC024 /* String+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
D75D0D4D25B37BCD001BC024 /* CombineSimpleNetworkAgent */ = {
isa = PBXGroup;
children = (
D75D0D3225B3596F001BC024 /* CombineSimpleNetworkAgent.swift */,
);
path = CombineSimpleNetworkAgent;
sourceTree = "<group>";
};
D75D0D5625B37EF5001BC024 /* Requests */ = {
isa = PBXGroup;
children = (
D7D362FF25B070330036EB48 /* RequestsBuilder.swift */,
D75D0D5125B37E97001BC024 /* WebAPI+Requests.swift */,
);
path = Requests;
sourceTree = "<group>";
};
D7D362EB25B06D530036EB48 /* Models */ = {
isa = PBXGroup;
children = (
D7D362ED25B06D710036EB48 /* UserSessionModel.swift */,
D7D362F225B06DA70036EB48 /* RequestModel.swift */,
D7D362ED25B06D710036EB48 /* Session.swift */,
);
path = Models;
sourceTree = "<group>";
};
D7D362EC25B06D590036EB48 /* WebAPI */ = {
isa = PBXGroup;
children = (
D7D362F225B06DA70036EB48 /* Request.swift */,
D7D362FF25B070330036EB48 /* WebAPI+Requests.swift */,
D75CE80425B0666400CD6AFD /* WebAPI.swift */,
D75D0D3525B35A22001BC024 /* WebAPI.swift */,
D75D0D5625B37EF5001BC024 /* Requests */,
);
path = WebAPI;
sourceTree = "<group>";
Expand Down Expand Up @@ -189,13 +228,18 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D7D362EE25B06D710036EB48 /* UserSessionModel.swift in Sources */,
D7D362F325B06DA70036EB48 /* Request.swift in Sources */,
D7D3630025B070330036EB48 /* WebAPI+Requests.swift in Sources */,
D75D0D4825B37BA4001BC024 /* Combine+Extensions.swift in Sources */,
D75D0D3325B3596F001BC024 /* CombineSimpleNetworkAgent.swift in Sources */,
D7D362EE25B06D710036EB48 /* Session.swift in Sources */,
D75D0D5225B37E97001BC024 /* WebAPI+Requests.swift in Sources */,
D7D362F325B06DA70036EB48 /* RequestModel.swift in Sources */,
D75D0D4925B37BA4001BC024 /* Request+URLRequest.swift in Sources */,
D7D3630025B070330036EB48 /* RequestsBuilder.swift in Sources */,
D73FB5A925B03031004F63EE /* AutenticationVC.swift in Sources */,
D73FB5A525B03031004F63EE /* AppDelegate.swift in Sources */,
D73FB5A725B03031004F63EE /* SceneDelegate.swift in Sources */,
D75CE80525B0666400CD6AFD /* WebAPI.swift in Sources */,
D75D0D4225B37B71001BC024 /* String+Extensions.swift in Sources */,
D75D0D3625B35A22001BC024 /* WebAPI.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Loading

0 comments on commit 44ae5de

Please sign in to comment.