Skip to content

Commit

Permalink
support secure enclave
Browse files Browse the repository at this point in the history
* Changes for encryption key management

Segregated the code for encryption key generation and data signing to separate class to enable scalability to other encryption key generation and storage techniques.

* Changing the interface descriptions

Changed the interface descriptions to match the new changes related to key generation handler

* Cleaning up spare code

Removing test code for secure enclave

* Changes related to secure enclave integratio

* Changes for secure enclave

- Fixed issue with creating jwk
- code cleanup and adding necessary comments
  • Loading branch information
ArunRajasekhar84 authored Jul 15, 2024
1 parent a4ec354 commit 711c3aa
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 35 deletions.
5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/keefertaylor/Base58Swift.git", branch: "master"),
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.8.1"),
.package(url: "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", .upToNextMajor(from: "2024.3.1"))
.package(url: "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", .upToNextMajor(from: "2024.3.1")),
.package(url: "https://github.com/airsidemobile/JOSESwift.git", from: "2.3.0")
],
targets: [
.target(
name: "eudiWalletOidcIos",
dependencies: ["Base58Swift", "CryptoSwift", "PresentationExchangeSdkiOS"]),
dependencies: ["Base58Swift", "CryptoSwift", "PresentationExchangeSdkiOS", "JOSESwift"]),
.testTarget(
name: "eudi-wallet-oidc-iosTests",
dependencies: ["eudiWalletOidcIos"]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,27 @@ import CryptoKit

public class CryptoKitHandler:NSObject, SecureKeyProtocol{

public var keyStorageType: SecureKeyTypes = .cryptoKit

public func generateSecureKey() -> SecureKeyData?{
let privateKey = P256.Signing.PrivateKey()
return SecureKeyData(publicKey: privateKey.publicKey.rawRepresentation, privateKey: privateKey.rawRepresentation)
}

public func sign(data: Data, withKey privateKey: Data?) -> Data?{

public func sign(payload: String, header: Data, withKey privateKey: Data?) -> String?{
if let privateKeyData = privateKey{
do{
let privateKey = try P256.Signing.PrivateKey(rawRepresentation: privateKeyData)
let signedData = try privateKey.signature(for: data)
return signedData.rawRepresentation

let payloadData = Data(payload.utf8)
let unsignedToken = "\(header.base64URLEncodedString()).\(payloadData.base64URLEncodedString())"
if let data = unsignedToken.data(using: .utf8){
let privateKey = try P256.Signing.PrivateKey(rawRepresentation: privateKeyData)
let signedData = try privateKey.signature(for: data)
let idToken = "\(unsignedToken).\(signedData.rawRepresentation.base64URLEncodedString())"
return idToken
}

}
catch{
return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// File.swift
//
//
// Created by Arun Raj on 27/06/24.
//

import Foundation

public class SecureEnclaveHandler: NSObject, SecureKeyProtocol{

var privateKeyLabel = ""
var publicKeyLabel = ""
var organisationID = ""
var secureEnclaveHandler: SecureEnclave?
public var keyStorageType: SecureKeyTypes = .cryptoKit

public init(organisationID: String) {
super.init()
self.organisationID = organisationID
self.keyStorageType = .secureEnclave
}

//Creating secure enclave instance with unique identifer for the keys
private func createSecureEnclaveHandlerFor() -> Bool{
if !organisationID.isEmpty{
secureEnclaveHandler = nil
privateKeyLabel = "com.EudiWallet.\(organisationID).PrivateKey"
secureEnclaveHandler = SecureEnclave(privateKeyApplicationTag: privateKeyLabel)
return true
} else{
// invalid organisation id
return false
}
}

//Generate private and public keys from secure enclave and pass the public key back
//private key is stored securely within secure enclave is not accessible directly
public func generateSecureKey() -> SecureKeyData?{
if createSecureEnclaveHandlerFor(){
do{
let anyExistingKey = try SecureEnclave.loadKeyPair(with: privateKeyLabel)

if let publicKeyData = convertSecKeyToData(key: anyExistingKey.publicKey){
return SecureKeyData(publicKey: publicKeyData, privateKey: nil)
}
}
catch{

do{
let newKeys = try SecureEnclave.generateKeyPair(with: privateKeyLabel)
if let publicKeyData = convertSecKeyToData(key: newKeys.publicKey){
return SecureKeyData(publicKey: publicKeyData, privateKey: nil)
}
}
catch{
print("Error retrieving the key")
return nil
}

}

}
return nil
}

func convertSecKeyToData(key: SecKey) -> Data?{
var error: Unmanaged<CFError>?
guard let publicKeydata = SecKeyCopyExternalRepresentation(key, &error) as? Data else {
return nil
}
return publicKeydata
}

public func sign(payload: String, header: Data, withKey privateKey: Data?) -> String?{
if createSecureEnclaveHandlerFor(){
do{
if let signedData = try secureEnclaveHandler?.sign(payload, header: header){

return signedData
}
}
catch{
return nil
}
}
return nil
}

public func getJWK(publicKey: Data) -> [String:Any]?{
let jwk = secureEnclaveHandler?.getJWK(publicKey: publicKey)
return jwk
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//Class implementing the high level key generation and signing of data
//using JOSESwift library for secure enclave

import Foundation
import Security
import JOSESwift


enum SecureEnclaveError: Error {
case couldNotLoadKeyPair
case couldNotGenerateKeyPair(description: String)
case couldNotCreateSigner
case couldNotCreateVerifier
}

class SecureEnclave {
let keyPair: KeyPair

init(privateKeyApplicationTag: String) {
do {
keyPair = try SecureEnclave.loadKeyPair(with: privateKeyApplicationTag)
} catch {
keyPair = try! SecureEnclave.generateKeyPair(with: privateKeyApplicationTag)
}
}

//signing of data securely with the private key
func sign(_ message: String, header: Data) throws -> String {
if let headerParams = JWSHeader(header){
let payload = Payload(message.data(using: .utf8)!)

guard let signer = Signer(signingAlgorithm: .ES256, privateKey: keyPair.privateKey) else {
throw SecureEnclaveError.couldNotCreateSigner
}

return try JWS(header: headerParams, payload: payload, signer: signer).compactSerializedString
}else{
throw SecureEnclaveError.couldNotCreateSigner
}
}

func verify(_ compactSerialization: String) throws -> Bool {
guard let verifier = Verifier(verifyingAlgorithm: .ES256, publicKey: keyPair.publicKey) else {
throw SecureEnclaveError.couldNotCreateVerifier
}

let jws = try JWS(compactSerialization: compactSerialization)

return jws.isValid(for: verifier)
}

}

extension SecureEnclave {
typealias KeyPair = (privateKey: SecKey, publicKey: SecKey)

//Load the private and public keys if already available in secure enclave
static func loadKeyPair(with applicationTag: String) throws -> KeyPair {
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: applicationTag,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecReturnRef as String: true
]

var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else {
throw SecureEnclaveError.couldNotLoadKeyPair
}

let privateKey = item as! SecKey
let publicKey = SecKeyCopyPublicKey(privateKey)!

return (privateKey, publicKey)
}

//generate new pair of public and private keys from secure enclave
static func generateKeyPair(with applicationTag: String) throws -> KeyPair {
let access = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.privateKeyUsage,
nil
)!

let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: applicationTag,
kSecAttrAccessControl as String: access
]
]

var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw SecureEnclaveError.couldNotGenerateKeyPair(
description: error!.takeRetainedValue().localizedDescription
)
}

let publicKey = SecKeyCopyPublicKey(privateKey)!

return (privateKey, publicKey)
}

//create jason web key for the given public key
func getJWK(publicKey:Data) -> [String:Any]?{
let jwk = try! ECPublicKey(publicKey: publicKey)
if let jsonData = jwk.jsonData(){
if let jwkDict = convertToDictionary(data: jsonData){
return jwkDict
}
}
return nil
}

func convertToDictionary(data: Data) -> [String: Any]? {

do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}

return nil
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
//
// File.swift
//
//
// SecureKeyProtocol.swift
// A protocol class to be implemented by the encrytion key generation classes
// to provide standardised api to apps
// Created by Arun Raj on 27/06/24.
//

import Foundation

//Enum to identify the type of key generation class used like CryptoKitHandler, SecureEnclaveHandler
public enum SecureKeyTypes{
case cryptoKit
case secureEnclave
}

public struct SecureKeyData{
public var publicKey: Data
public var privateKey: Data?
Expand All @@ -18,7 +24,13 @@ public struct SecureKeyData{
}

public protocol SecureKeyProtocol: NSObjectProtocol{
func generateSecureKey() -> SecureKeyData?
func sign(data: Data, withKey privateKey: Data?) -> Data?

var keyStorageType: SecureKeyTypes { get set } //value for storing the key generation type used
func generateSecureKey() -> SecureKeyData? //for generating new private & public keys
func sign(payload: String, header: Data, withKey privateKey: Data?) -> String? //sign data
func getJWK(publicKey:Data) -> [String:Any]? //get the json web key for did generation
}

extension SecureKeyProtocol{
public func getJWK(publicKey:Data) -> [String:Any]? {return nil}

}
38 changes: 28 additions & 10 deletions Sources/eudiWalletOidcIos/Service/DidService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,35 @@ public class DidService {
public func createJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)?{

if let keys = keyHandler.generateSecureKey(){
let rawRepresentation = keys.publicKey
let x = rawRepresentation[rawRepresentation.startIndex..<rawRepresentation.index(rawRepresentation.startIndex, offsetBy: 32)]
let y = rawRepresentation[rawRepresentation.index(rawRepresentation.startIndex, offsetBy: 32)..<rawRepresentation.endIndex]
let jwk: [String: Any] = [
"crv": "P-256",
"kty": "EC",
"x": x.urlSafeBase64EncodedString(),
"y": y.urlSafeBase64EncodedString()
]
return (jwk, SecureKeyData(publicKey: keys.publicKey, privateKey: keys.privateKey))

if keyHandler.keyStorageType == .cryptoKit{
let rawRepresentation = keys.publicKey
let x = rawRepresentation[rawRepresentation.startIndex..<rawRepresentation.index(rawRepresentation.startIndex, offsetBy: 32)]
let y = rawRepresentation[rawRepresentation.index(rawRepresentation.startIndex, offsetBy: 32)..<rawRepresentation.endIndex]
let jwk: [String: Any] = [
"crv": "P-256",
"kty": "EC",
"x": x.urlSafeBase64EncodedString(),
"y": y.urlSafeBase64EncodedString()
]
if let theJSONData = try? JSONSerialization.data(
withJSONObject: jwk,
options: []) {
let theJSONText = String(data: theJSONData,
encoding: .ascii)
print("JSON string = \(theJSONText!)")
}
return (jwk, SecureKeyData(publicKey: keys.publicKey, privateKey: keys.privateKey))
} else{

if let jsonDict = keyHandler.getJWK(publicKey: keys.publicKey){
return (jsonDict, SecureKeyData(publicKey: keys.publicKey, privateKey: keys.privateKey))
}
}

}
return nil
}


}
Loading

0 comments on commit 711c3aa

Please sign in to comment.