LCOV - code coverage report
Current view: top level - SwiftySimpleKeychain_RSAHelper/Extensions - SwiftySimpleKeychain+RSAHelper.swift (source / functions) Hit Total Coverage
Test: lcov.info Lines: 129 136 94.9 %
Date: 2022-03-29 11:59:53 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //
       2             : // SwiftySimpleKeychain+RSAHelper
       3             : // SwiftySimpleKeychain_RSAHelper
       4             : //
       5             : // Copyright (c) 2022 Ezequiel (Kimi) Aceto (https://eaceto.dev). Based on Auth0's SimpleKeychain
       6             : //
       7             : // Permission is hereby granted, free of charge, to any person obtaining a copy
       8             : // of this software and associated documentation files (the "Software"), to deal
       9             : // in the Software without restriction, including without limitation the rights
      10             : // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      11             : // copies of the Software, and to permit persons to whom the Software is
      12             : // furnished to do so, subject to the following conditions:
      13             : //
      14             : // The above copyright notice and this permission notice shall be included in
      15             : // all copies or substantial portions of the Software.
      16             : //
      17             : // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      18             : // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      19             : // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      20             : // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      21             : // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      22             : // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      23             : // THE SOFTWARE.
      24             : 
      25             : import SwiftySimpleKeychain
      26             : 
      27             : #if canImport(Foundation)
      28             : import Foundation
      29             : #endif
      30             : 
      31             : #if canImport(Security)
      32             : import Security
      33             : #endif
      34             : 
      35             : /**
      36             :  *  An extension of SwiftySimpleKeychain to deal with generation, retrieving and deletion of
      37             :  *  public / private RSA keys using the Keychain.
      38             :  *  It honours the configuration (and access control) of **SwiftySimpleKeychain**
      39             :  */
      40             : public extension SwiftySimpleKeychain {
      41             :     
      42             :     /**
      43             :      * Generates a RSA key pair with the specified key size and stores it in the Keychain
      44             :      *
      45             :      * Usage:
      46             :      * ```swift
      47             :      *  keychain = SwiftySimpleKeychain(with: "service-name")
      48             :      *  let result = keychain.generateRSAKeyPair()
      49             :      *  if case let .failure(error) = result {
      50             :      *      print(error)
      51             :      *  }
      52             :      * ```
      53             :      *
      54             :      * - Parameters:
      55             :      *  - parameter length: the length (int bits) of the RSA keys. Defaults to 2048 bits
      56             :      *  - parameter publicKeyTag: a tag (identifier) for the public key. Defaults to *public*
      57             :      *  - parameter privateKeyTag: a tag (identifier) for the private key. Defaults to *private*
      58             :      *  - return a **VoidErrorResult**
      59             :      */
      60             :     func generateRSAKeyPair(with length: SwiftySimpleKeychainRSAKeySize = .size2048Bits,
      61             :                             publicKeyTag: String = "public",
      62             :                             privateKeyTag : String = "private"
      63           9 :     ) -> VoidErrorResult {
      64           9 :         if (publicKeyTag.isEmpty || privateKeyTag.isEmpty) {
      65           2 :             return .failure(.wrongParameter)
      66           7 :         }
      67           7 :         
      68           7 :         guard let publicKeyTagData = publicKeyTag.data(using: .utf8) else {
      69           0 :             return .failure(.decode)
      70           7 :         }
      71           7 :         
      72           7 :         guard let privateKeyTagData = privateKeyTag.data(using: .utf8) else {
      73           0 :             return .failure(.decode)
      74           7 :         }
      75           7 :         
      76           7 :         var pairAttr = [
      77           7 :             kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
      78           7 :             kSecAttrKeySizeInBits as String: length.rawValue,
      79           7 :         ] as [String:Any]
      80           7 :         
      81           7 :         let privateAttr = [
      82           7 :             kSecAttrIsPermanent as String: true,
      83           7 :             kSecAttrApplicationTag as String: privateKeyTagData,
      84           7 :         ] as [String:Any]
      85           7 :         
      86           7 :         let publicAttr = [
      87           7 :             kSecAttrIsPermanent as String: true,
      88           7 :             kSecAttrApplicationTag as String: publicKeyTagData,
      89           7 :         ] as [String:Any]
      90           7 :         
      91           7 :         pairAttr[kSecPrivateKeyAttrs as String] = privateAttr
      92           7 :         pairAttr[kSecPublicKeyAttrs as String] = publicAttr
      93           7 :         
      94           7 :         var publicKeyRef : SecKey?
      95           7 :         var privateKeyRef : SecKey?
      96           7 : 
      97           7 :         let status = SecKeyGeneratePair(pairAttr as CFDictionary, &publicKeyRef, &privateKeyRef);
      98           7 : 
      99           7 :         if status == errSecSuccess {
     100           7 :             return .success(())
     101           7 :         }
     102           0 :         return .failure(SwiftySimpleKeychainError.from(status))
     103           7 :     }
     104             :     
     105             :     /**
     106             :      * Finds a Key stored in the **Keychain** and returns a **DataErrorResult**
     107             :      * Use this method if you would like to know why fetching a key fails, and an error
     108             :      * is present in the *failure* response.
     109             :      *
     110             :      * Usage:
     111             :      * ```swift
     112             :      * let result = keychain.rsaKeyDataResult(with: "publicKeyTag")
     113             :      * guard case let .success(publicKeyData) = publicKeyDataResult else {
     114             :      *     if case let .failure(error) = publicKeyDataResult {
     115             :      *         print(error)
     116             :      *         fail("Fail to get Public Key. \(error)")
     117             :      *     }
     118             :      *     return
     119             :      * }
     120             :      * ```
     121             :      *
     122             :      * - Parameters:
     123             :      *  - parameter tag: a tag (identifier) for the key.
     124             :      *  - return a **DataErrorResult**
     125             :      */
     126           6 :     func rsaKeyDataResult(with tag: String) -> DataErrorResult {
     127           6 :         let queryResult = keyQuery(with: tag)
     128           6 :         var query: [String:Any]
     129           6 :         
     130           6 :         switch queryResult {
     131           6 :         case .success(let dict): query = dict
     132           6 :         case .failure(let err): return .failure(err)
     133           6 :         }
     134           6 :         
     135           6 :         query[kSecReturnData as String] = true
     136           6 :         
     137           6 :         var dataRef: CFTypeRef?
     138           6 :         let status = SecItemCopyMatching(query as CFDictionary, &dataRef)
     139           6 :         
     140           6 :         if status != errSecSuccess {
     141           0 :             return .failure(SwiftySimpleKeychainError.from(status))
     142           6 :         }
     143           6 :         
     144           6 :         guard let data = dataRef as? Data else {
     145           0 :             return .failure(SwiftySimpleKeychainError.allocation)
     146           6 :         }
     147           6 :         return .success(data)
     148           6 :     }
     149             :     
     150             :     /**
     151             :      * Finds a Key stored in the **Keychain** and returns a **Data** if found
     152             :      * Use this method if you want a simple response (data or zero) regardless of error on failure
     153             :      *
     154             :      * Usage:
     155             :      * ```swift
     156             :      * let data = keychain.rsaKeyData(with: "publicKeyTag")
     157             :      * ```
     158             :      *
     159             :      * - Parameters:
     160             :      *  - parameter tag: a tag (identifier) for the key.
     161             :      *  - return a **Data** if found. If not, returns nil
     162             :      */
     163           4 :     func rsaKeyData(with tag: String) -> Data? {
     164           4 :         let result = rsaKeyDataResult(with: tag)
     165           4 :         switch result {
     166           4 :         case .success(let data): return data
     167           4 :         case .failure(_ ): return nil
     168           4 :         }
     169           4 :     }
     170             :     
     171             :     /**
     172             :      * Cheks if there is a key with the provided **tag**
     173             :      *
     174             :      * - Parameters:
     175             :      *  - parameter tag: a tag (identifier) for the key.
     176             :      *  - return **true** if the key exists
     177             :      */
     178           4 :     func hasRSAKey(with tag: String) -> Bool {
     179           4 :         let queryResult = keyQuery(with: tag)
     180           4 :         var query: [String:Any]
     181           4 :         
     182           4 :         switch queryResult {
     183           4 :         case .success(let dict): query = dict
     184           4 :         case .failure(_): return false
     185           4 :         }
     186           4 :         
     187           4 :         query[kSecReturnData as String] = false
     188           4 :         
     189           4 :         let status = SecItemCopyMatching(query as CFDictionary, nil)
     190           4 :         return status == errSecSuccess
     191           4 :     }
     192             :     
     193             :     /**
     194             :      * Deletes a RSA Key identified with the provided **tag**
     195             :      *
     196             :      * Usage:
     197             :      * ```swift
     198             :      * let deleted = keychain.deleteRSAKey(with: "publicKeyTag")
     199             :      * ```
     200             :      *
     201             :      * - Parameters:
     202             :      *  - parameter tag: a tag (identifier) for the key.
     203             :      *  - return **true** if the key exists and was deleted
     204             :      */
     205          14 :     func deleteRSAKey(with tag: String) -> Bool {
     206          14 :         let queryResult = keyQuery(with: tag)
     207          14 :         var query: [String:Any]
     208          14 :         
     209          14 :         switch queryResult {
     210          14 :         case .success(let dict): query = dict
     211          14 :         case .failure(_): return false
     212          14 :         }
     213          14 :         
     214          14 :         let status = SecItemDelete(query as CFDictionary)
     215          14 :         return status == errSecSuccess
     216          14 :     }
     217             :     
     218             :     /**
     219             :      * Finds a Key stored in the **Keychain** and returns a **SecKeyErrorResult**
     220             :      *
     221             :      * ```swift
     222             :      *  let result = keychain.rsaSecKeyResult(with: "publicTag")
     223             :      *  if case let .failure(error) = result {
     224             :      *      print(error)
     225             :      *  }
     226             :      * ```
     227             :      *
     228             :      * - Parameters:
     229             :      *  - parameter tag: a tag (identifier) for the key.
     230             :      *  - return a **SecKeyErrorResult**
     231             :      */
     232           4 :     func rsaSecKeyResult(with tag: String) -> SecKeyErrorResult {
     233           4 :         let queryResult = keyQuery(with: tag)
     234           4 :         var query: [String:Any]
     235           4 :         
     236           4 :         switch queryResult {
     237           4 :         case .success(let dict): query = dict
     238           4 :         case .failure(let err): return .failure(err)
     239           4 :         }
     240           4 :         
     241           4 :         query[kSecReturnData as String] = true
     242           4 :             
     243           4 :         var result: CFTypeRef?
     244           4 :         let status = SecItemCopyMatching(query as CFDictionary, &result)
     245           4 :         if status == errSecSuccess {
     246           4 :             return .success(result as! SecKey)
     247           4 :         }
     248           0 :         return .failure(SwiftySimpleKeychainError.from(status))
     249           4 :     }
     250             :     
     251             :     /**
     252             :      * Finds a Key stored in the **Keychain** and returns a **SecKeyErrorResult**
     253             :      *
     254             :      * ```swift
     255             :      *  if let publicKey = keychain.rsaSecKey(with: "publicTag") {
     256             :      *
     257             :      *  }
     258             :      * ```
     259             :      *
     260             :      * - Parameters:
     261             :      *  - parameter tag: a tag (identifier) for the  key.
     262             :      *  - return a **SecKey** if the RSA key exists.
     263             :      */
     264           2 :     func rsaSecKey(with tag: String) -> SecKey? {
     265           2 :         switch rsaSecKeyResult(with: tag) {
     266           2 :         case .success(let secKey): return secKey
     267           2 :         case .failure: return nil
     268           2 :         }
     269           2 :     }
     270             : }
     271             : 
     272             : extension SwiftySimpleKeychain {
     273          29 :     internal func keyQuery(with tag: String) -> Result<[String:Any],SwiftySimpleKeychainError> {
     274          29 :         if (tag.isEmpty) {
     275           1 :             return .failure(.wrongParameter)
     276          28 :         }
     277          28 :         
     278          28 :         guard let tagData = tag.data(using: .utf8) else {
     279           0 :             return .failure(.decode)
     280          28 :         }
     281          28 :         
     282          28 :         return .success([
     283          28 :             kSecClass as String: kSecClassKey,
     284          28 :             kSecAttrApplicationTag as String: tagData,
     285          28 :             kSecAttrType as String: kSecAttrKeyTypeRSA
     286          28 :         ] as [String:Any])
     287          28 :     }
     288             : }

Generated by: LCOV version 1.15