Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update readme #8

Merged
merged 7 commits into from
Jan 14, 2025
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
86 changes: 77 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,92 @@
[![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

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.
The XenIssuing SDK provides a secure way to handle sensitive operations in your iOS applications. This SDK includes:
- **SecureSession**: A module that ensures encrypted communication between your application and Xendit's services.

## Prerequisites

To utilize the XenIssuing SDK, a public key granted by Xendit is required. You can obtain this key by contacting Xendit directly.
- iOS 10.15 or later
- Swift 5.0 or later
- A public key from Xendit (Contact Xendit to obtain this)

## Usage

### Establishing Secure Sessions

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:
### Creating a Secure Session

```swift
import Xenissuing

let secureSession = try Xenissuing.createSecureSession(xenditPublicKeyData: Data(base64Encoded: validPublicKey)!)
let sessionId = secureSession.getKey().base64EncodedString()
let publicKey = """
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... // Your RSA public key without header/footer
"""

do {
// Create secure session
let secureSession = try Xenissuing.createSecureSession(
xenditPublicKeyData: Data(base64Encoded: publicKey)!
)

// Get session ID for API authentication
let sessionId = secureSession.getSessionId().base64EncodedString()

// Important: URL encode the session ID as it will be used as a URL parameter
let allowedCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
let encodedSessionId = sessionId.addingPercentEncoding(withAllowedCharacters: allowedCharacters) ?? ""

// Use encodedSessionId in API requests
let apiUrl = "https://api.xendit.co/card_issuing/cards/{cardId}/pan?session_id=\(encodedSessionId)"
} catch {
print("Error:", error)
}
```

### Public Key Format

The public key should be:
- An RSA public key provided by Xendit
- Without the "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----" headers
- A single continuous string (can use Swift multi-line string format for readability)

Example format:
```swift
let publicKey = """
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
"""
```

### Session ID Usage

let decryptedData = secureSession.decryptCardData(secret: secret, iv: iv)
The session ID must be URL encoded because:
- It contains base64 characters that may include '+' and '/'
- It will be used as a URL parameter in API requests
- URL encoding ensures safe transmission of the session ID in HTTP requests

Example API usage:
```swift
let apiUrl = "https://api.xendit.co/card_issuing//cards/\(cardId)/pan?session_id=\(encodedSessionId)"
```

### Decrypting Card Data

When you receive encrypted card data from Xendit's API:

```swift
do {
let decryptedData = try secureSession.decryptCardData(
secret: encryptedCardData, // Base64 encoded encrypted data
iv: initializationVector // Base64 encoded IV
)

// Process the decrypted card data
let cardInfo = String(data: decryptedData, encoding: .utf8)
} catch {
print("Decryption error:", error)
}
```

## Support

For issues, questions, or assistance, please reach out to the XenIssuing team at Xendit.
- Email: [email protected]
- API Documentation: https://developers.xendit.co
66 changes: 54 additions & 12 deletions Sources/Xenissuing/SecureSession/SecureSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,30 @@ public class SecureSession: Crypto {
/**
Returns the encrypted session key.
*/
public func getKey() -> Data {
public func getSessionId() -> Data {
// This should be used for API authentication
return self.secureSession!.sealed
}

public func getSessionKey() -> Data {
// This should be used for validation
return self.secureSession!.key
}

// deprecate ambiguous method
@available(*, deprecated, message: "Use getSessionId() instead")
public func getEncryptedKey() -> Data {
return getSessionId()
}

// deprecate ambiguous method
@available(*, deprecated, message: "Use getSessionKey() instead")
public func getKey() -> Data {
return getSessionKey()
}

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

/**
Expand Down Expand Up @@ -111,9 +129,17 @@ public class SecureSession: Crypto {
*/
internal func generateSessionId(sessionKey: Data) throws -> SecuredSession {
do {
// 1. Base64 encode the session key
let base64Key = sessionKey.base64EncodedString()

// 2. Convert to raw bytes
let keyBytes = Data(base64Key.utf8)

// 3. Encrypt using RSA-OAEP-SHA256
let sealed = try self.xenditPublicKey.encrypt(
algorithm: .rsaEncryptionOAEPSHA256,
plaintext: sessionKey)
plaintext: keyBytes)

return SecuredSession(key: sessionKey, sealed: sealed)
} catch {
throw XenError.generateSessionIdError("")
Expand All @@ -122,8 +148,8 @@ public class SecureSession: Crypto {

/**
Encrypts data following AES-GCM scheme.
- Parameter plain: the data to encrupt.
- Parameter iv: initilization vector randomly generated
- Parameter plain: the data to encrypt.
- Parameter iv: initialization vector randomly generated
- Parameter sessionKey: sessionKey used to encrypt
- Throws: `XenError.encryptionError`
if there was any issue during encryption.
Expand All @@ -145,7 +171,7 @@ public class SecureSession: Crypto {
Decrypts data that has been encrypted following AES-GCM scheme.
- Parameter secret: Secret encoded in base64 format.
- Parameter sessionKey: sessionKey used to encrypt.
- Parameter iv: initilization vector or nonce in base64 format
- Parameter iv: initialization vector or nonce in base64 format
- Throws: `XenError.decryptionError`
if there was any issue during decryption.
- Returns: The decrypted text.
Expand Down Expand Up @@ -186,7 +212,8 @@ public class SecureSession: Crypto {
if status != 0 {
return nil
}
return keyRef as! SecKey
return (keyRef as! SecKey)

}

private static func getKeyFromKeychainAsData(tag: String) -> Data? {
Expand Down Expand Up @@ -249,10 +276,25 @@ private extension SecKey {

func encrypt(algorithm: SecKeyAlgorithm, plaintext: Data) throws -> Data {
var error: Unmanaged<CFError>?
let ciphertextO = SecKeyCreateEncryptedData(self, algorithm,
plaintext as CFData, &error)
if let error = error?.takeRetainedValue() { throw error }
guard let ciphertext = ciphertextO else { throw XenError.encryptRSAError("") }
return ciphertext as Data

// 1. Verify algorithm support
guard SecKeyIsAlgorithmSupported(self, .encrypt, .rsaEncryptionOAEPSHA256) else {
throw XenError.encryptRSAError("RSA-OAEP-SHA256 not supported")
}

// 2. Perform encryption with OAEP padding
guard let ciphertext = SecKeyCreateEncryptedData(
self,
.rsaEncryptionOAEPSHA256,
plaintext as CFData,
&error
) as Data? else {
if let error = error?.takeRetainedValue() {
throw error
}
throw XenError.encryptRSAError("Encryption failed")
}

return ciphertext
}
}
7 changes: 6 additions & 1 deletion Tests/XenissuingTests/SecureSessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ final class SecureSessionTests: XCTestCase {
let xcrypt = try! SecureSession(xenditPublicKeyData: publicKeyData)
let sessionKey = try! xcrypt.generateRandom()
let sessionId = try! xcrypt.generateSessionId(sessionKey: sessionKey)

// Decrypt the sealed data
let decryptedBytes = try! privateKey.decrypt(algorithm: .rsaEncryptionOAEPSHA256, ciphertext: sessionId.sealed)
let decryptedData = Data(decryptedBytes)
XCTAssertEqual(sessionKey.base64EncodedString(), decryptedData.base64EncodedString())

// Convert decrypted bytes back to string and compare with original base64 encoded session key
let decryptedString = String(data: decryptedData, encoding: .utf8)!
XCTAssertEqual(sessionKey.base64EncodedString(), decryptedString)
}

func testDecrypt() {
Expand Down
Loading