Skip to content

Commit

Permalink
Merge pull request #5 from xendit/refactor/secure-session
Browse files Browse the repository at this point in the history
[Refactor] Create Secure Sessions
  • Loading branch information
sammous authored Aug 10, 2023
2 parents f7ac9cd + cfe9e85 commit 37ec746
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 50 deletions.
40 changes: 11 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,24 @@
[![Swift Package Manager compatible](https://img.shields.io/badge/Swift_Package_Manager-compatible-brightgreen.svg?style=flat&colorA=28a745&&colorB=4E4E4E)](https://github.com/apple/swift-package-manager)
# Xenissuing

This SDK comprises of the following modules :
- SecureSession: this module handles encryption between XenIssuing and your iOS application.
## Requirements
The XenIssuing SDK includes a collection of modules designed to handle sensitive operations with ease and security in your iOS applications. Notably:
- SecureSession: This module is responsible for ensuring encrypted communication between the XenIssuing SDK and your iOS application.

To be able to use XenIssuing, you will need to use the public key provided by Xendit.
## Prerequisites

## Usage


### SecureSession

SecureSession is a module to help you set up encryption between XenIssuing and your application.

It includes several methods:
- `generateSessionId` will encrypt a session key randomly generated used for asymmetric encryption with Xenissuing.
- `encrypt` would be used when setting sensitive data.
- `decrypt` would be used whenever receiving sensitive data from Xenissuing.
To utilize the XenIssuing SDK, a public key granted by Xendit is required. You can obtain this key by contacting Xendit directly.

## Usage

### Session ID
### Establishing Secure Sessions

```swift
let xen = try! Xenissuing(xenditPublicKeyData: Data(base64Encoded: "BASE64_PUBLIC_KEY", options: .ignoreUnknownCharacters)!)
let sessionKey = try! xen.generateRandom()
let sessionId = try! xen.generateSessionId(sessionKey: sessionKey.base64EncodedString().data(using: .utf8)!)
let sealed = sessionId.sealed // Data
```

### Encryption
The SecureSession module aids in establishing an encrypted communication link between the XenIssuing SDK and your application. Below is a Swift example demonstrating how to create a secure session and decrypt card data:

```swift
import Xenissuing

let xen = try! Xenissuing(xenditPublicKeyData: Data(base64Encoded: "BASE64_PUBLIC_KEY", options: .ignoreUnknownCharacters)!)
let nonce = try! xen.generateRandom()
let privateKey = try! xen.generateRandom()
let encrypted = try! xen.encrypt(plain: "hTlNMg4CJZVdTIfXBehfxcU0XQ==".data(using: .utf8)!, iv: nonce, sessionKey: privateKey)
let sealed = encrypted.sealed // sealed message
let secureSession = try Xenissuing.createSecureSession(xenditPublicKeyData: Data(base64Encoded: validPublicKey)!)
let sessionId = secureSession.getKey().base64EncodedString()

let decryptedData = secureSession.decryptCardData(secret: secret, iv: iv)
```
33 changes: 23 additions & 10 deletions Sources/Xenissuing/SecureSession/SecureSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import Security

protocol Crypto {
func generateRandom(size: Int) throws -> Data
func generateSessionId(sessionKey: Data) throws -> EncryptedMessage
func encrypt(plain: Data, iv: Data, sessionKey: Data) throws -> EncryptedMessage
func generateSessionId(sessionKey: Data) throws -> SecuredSession
func encrypt(plain: Data, iv: Data, sessionKey: Data) throws -> SecuredSession
func decrypt(secret: String, sessionKey: Data, iv: String) throws -> Data
}

/// Encapsulates encrypted message and key used as encryption key.
public struct EncryptedMessage {
public struct SecuredSession {
internal let key: Data
public let sealed: Data
public init(key: Data, sealed: Data) {
Expand All @@ -27,6 +27,7 @@ public struct EncryptedMessage {
public class SecureSession: Crypto {
/// The key provided by Xendit.
let xenditPublicKey: SecKey
var secureSession: SecuredSession?

/**
Initializes an object with the provided public key data and tag.
Expand Down Expand Up @@ -70,6 +71,19 @@ public class SecureSession: Crypto {
throw XenError.convertKeyDataError("")
}
}
let sKey = try self.generateRandom()
self.secureSession = try self.generateSessionId(sessionKey: sKey)
}

/**
Returns the encrypted session key.
*/
public func getKey() -> Data {
return self.secureSession!.sealed
}

public func decryptCardData(secret: String, iv: String) throws -> Data {
return try self.decrypt(secret: secret, sessionKey: self.getKey(), iv: iv)
}

/**
Expand All @@ -78,7 +92,7 @@ public class SecureSession: Crypto {
if there was any issue when trying to generate the symmetric key.
- Returns: the random session key.
*/
public func generateRandom(size _: Int = 32) throws -> Data {
internal func generateRandom(size _: Int = 32) throws -> Data {
var randomBytes = [UInt8](repeating: 0, count: 32)
let result = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)
guard result == errSecSuccess else {
Expand All @@ -90,18 +104,17 @@ public class SecureSession: Crypto {
/**
Generates Session Id following AES-CBC scheme using the key provided by Xendit.
IV used is 16 bytes size.
- Parameter xenditKey: Secret key `Data` (provided by Xendit).
- Parameter sessionKey: Random key `Data`.
- Throws: `XenError.encryptionError`
if there was any issue during encryption.
- Returns: The encrypted text
*/
public func generateSessionId(sessionKey: Data) throws -> EncryptedMessage {
internal func generateSessionId(sessionKey: Data) throws -> SecuredSession {
do {
let sealed = try self.xenditPublicKey.encrypt(
algorithm: .rsaEncryptionOAEPSHA256,
plaintext: sessionKey)
return EncryptedMessage(key: sessionKey, sealed: sealed)
return SecuredSession(key: sessionKey, sealed: sealed)
} catch {
throw XenError.generateSessionIdError("")
}
Expand All @@ -116,13 +129,13 @@ public class SecureSession: Crypto {
if there was any issue during encryption.
- Returns: The encrypted text
*/
public func encrypt(plain: Data, iv _: Data, sessionKey: Data) throws -> EncryptedMessage {
public func encrypt(plain: Data, iv _: Data, sessionKey: Data) throws -> SecuredSession {
do {
let iv = AES.randomIV(32)
let gcm = GCM(iv: iv, mode: .combined)
let aes = try AES(key: sessionKey.bytes, blockMode: gcm, padding: .noPadding)
let sealed = try aes.encrypt(plain.bytes)
return EncryptedMessage(key: sessionKey, sealed: Data(sealed))
return SecuredSession(key: sessionKey, sealed: Data(sealed))
} catch {
throw XenError.encryptionError("")
}
Expand All @@ -137,7 +150,7 @@ public class SecureSession: Crypto {
if there was any issue during decryption.
- Returns: The decrypted text.
*/
public func decrypt(secret: String, sessionKey: Data, iv: String) throws -> Data {
internal func decrypt(secret: String, sessionKey: Data, iv: String) throws -> Data {
do {
let secret = Data(base64Encoded: secret)!
let nonce = Data(base64Encoded: iv)!
Expand Down
15 changes: 6 additions & 9 deletions Sources/Xenissuing/Xenissuing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,18 @@ import Foundation
// MARK: - Xenissuing

@available(macOS 10.15, *)
public final class Xenissuing: SecureSession {
public enum Xenissuing {
/**
Initializes XenIssuing module.
Create a secure session used to generate a key and decrypt card data.

- Parameters:
- xenditPublicKeyData: Public Key.
- xenditPublicKeyTag: Public Key Tag. If provided, it will try to check first keychain to get the key data.

- Returns: Main module.
- Returns: Secure session object.
*/
override public init(xenditPublicKeyData: Data, xenditPublicKeyTag: String? = nil) throws {
do {
try super.init(xenditPublicKeyData: xenditPublicKeyData, xenditPublicKeyTag: xenditPublicKeyTag)
} catch {
throw error
}
public static func createSecureSession(xenditPublicKeyData: Data, xenditPublicKeyTag: String? = nil) throws -> SecureSession {
let secSession: SecureSession = try SecureSession(xenditPublicKeyData: xenditPublicKeyData, xenditPublicKeyTag: xenditPublicKeyTag)
return secSession
}
}
File renamed without changes.
20 changes: 18 additions & 2 deletions Tests/XenissuingTests/XenissuingTests.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import Crypto
import CryptoKit
import CryptoSwift
import Foundation
import Security
import XCTest
@testable import Xenissuing

final class XenissuingTests: XCTestCase {
let validPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArY3DXFJ2M0EHbsD9r+2XgFVtpYEQR5bxnQZVHVxtVzQP8u2cv/1APs2cft+8E682wKGY7SFUEsFsoqxoak7qsfXYL/mOdvQe6XDyNC7N6oo9Zb8dUKtuy8qPb1bVeTbxAwDVUzIdJpiRVI69fAGCW7aF3jTAV7Q+Z5qUTaLUFyKvu3+j8u/A58Nw5fjOENTLHBZRrXhFtQC1eql2O6FiQRJBDACYtzhyFBMyT/B7SKNPkEvLm1w4AQEWxxwL93B8vxstfpatbJJvorJaDEl/glncxJVtZ0lBeB3dkWdro/TrhpPD7CHKlBIUKRfvq1TgmMFs9SP90DxD9l9mE+AUAwIDAQAB"

func testValidPubicKey() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
let validPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArY3DXFJ2M0EHbsD9r+2XgFVtpYEQR5bxnQZVHVxtVzQP8u2cv/1APs2cft+8E682wKGY7SFUEsFsoqxoak7qsfXYL/mOdvQe6XDyNC7N6oo9Zb8dUKtuy8qPb1bVeTbxAwDVUzIdJpiRVI69fAGCW7aF3jTAV7Q+Z5qUTaLUFyKvu3+j8u/A58Nw5fjOENTLHBZRrXhFtQC1eql2O6FiQRJBDACYtzhyFBMyT/B7SKNPkEvLm1w4AQEWxxwL93B8vxstfpatbJJvorJaDEl/glncxJVtZ0lBeB3dkWdro/TrhpPD7CHKlBIUKRfvq1TgmMFs9SP90DxD9l9mE+AUAwIDAQAB"
XCTAssertNoThrow(try Xenissuing(xenditPublicKeyData: Data(base64Encoded: validPublicKey)!))
XCTAssertNoThrow(try Xenissuing.createSecureSession(xenditPublicKeyData: Data(base64Encoded: validPublicKey)!))
}

func testCreateSecureSession() throws {
let secureSession = try Xenissuing.createSecureSession(xenditPublicKeyData: Data(base64Encoded: validPublicKey)!)
XCTAssertNotNil(secureSession.secureSession)
}

func testSecureSessionGetKey() throws {
let secureSession = try Xenissuing.createSecureSession(xenditPublicKeyData: Data(base64Encoded: validPublicKey)!)
XCTAssertNotNil(secureSession.getKey())
}
}

0 comments on commit 37ec746

Please sign in to comment.