sensitivity.io with iOS and macOS

sensitivity.io libraries for iOS and macOS are Objective C wrapper libraries over our sensitivity.io C libraries that makes it easy to integrate in both Objective C and Swift applications.

sensitivity.io provides you with the following modules:

  • Sensitive Data Scanner (SDS) with which you can scan texts, data and files for threats

Each module has its own independent library:

  • libsensitivityio_objc_sds for SDS

and require the use of the following dependent libraries through which the setup of the module is done:

  • libsensitivityio_objc_base
  • libsensitivityio_objc_license

The sensitivity.io libraries provided for the iOS platform are static while the ones for macOS are dynamic.

1. Integrating module libraries into your iOS or macOS application

Follow the sensitivity.io intallation guide to download and install your module libraries:

Follow the steps provided in the tutorial that you can download here to set up your macOS or iOS application.

2. Module setup

Before attempting to use any sensitivity.io modules in your application, you need to first set it up, using the following steps:

  • initialize resources
  • load license
  • load settings

Resource initialization needs to be done only once in the application lifetime before any other operation, by using the class methods defined in ResourcesInitializer

Initialize the base resources first:

1
try ResourcesInitializer.initBase()

Then initialize the specific module resources defined in the corresponding ResourcesInitializer categories:

1
try ResourcesInitializer.initSds()

There are 2 modes in which you can perform your license and settings setup, depending on the way you need to handle your license and settings files: offline and online.

Note

You can have only 1 license and it is unique to your account. The license will be set globally for your application through the defined methods of the singleton LicenseLoader class.

2.1 Offline license & settings setup

Use offline setup when your license and settings don’t change and you provide them within the application bundle. In this case you need to load your license and settings using the respective loaders.

Log into your sensitivity.io account to download your license and settings files. You can find them in the application details of each createad project.

Note

You can have only 1 license and it is unique to your account. The license will be set globally for your application through the defined methods of the singleton LicenseLoader class.

You can manage the settings in the Protection Profile section of your sensitivity.io account. You’ll find you already have some predefined settings, but you can also define your own custom settings based on your own requirements. You can find more on protection profiles by following the link.

Select a protection profile and download its settings to use in your application.

Note

You can have several settings defined in your account, hence, in your application you can have several settings loaded at the same time through different instances of the SettingsLoader class.

Warning

Loading settings into a settings loader is a time consuming task that can block the UI if performed on main thread.

Load license using LicenseLoader:

1
try LicenseLoader.shared().loadLicenseFromFile(atPath: licenseFilePath)

or

1
try LicenseLoader.shared().loadLicense("license_content")

Use specific module settings loader classes to instantiate a SettingsLoader object:

Load module settings:

1
try settingsLoader.loadSettingsFromFile(atPath: settingsFilePath)

or

1
try settingsLoader.loadSettings("settings_content")

2.2 Online license & settings setup

Use online setup when your license and settings change frequently and are downloaded from your sensitivity.io account using the sensitivity.io retrievers.

All sensitivity.io retrievers are subclasses of HttpRetriever and are set up globally through its class methods with your credentials and service details. Setting your credentials is mandatory, if not set the retrievers will not work. If no service details are set, the default values will be used.

You can find your credentials:

  • Auth Key
  • Account ID
  • Project ID
  • Application ID

on the Account Summary panel in your sensitivity.io account.

Note

When using the retrieveToFile methods of the sensitivity.io retrievers, you first need to set the destination file for it to work.

Note

When using the retrieveToFileAndNotifyLoader or retrieveToStringAndNotifyLoader of the specific module settings retrievers, you need to provide the retriever with the settings loader that will be notified to load the downloaded resource. If the settings loader was not provided then you need to manually load the downloaded resources.

Warning

Retrieving data using the sensitivity.io retrievers represents a HTTPS request to a sensitivity.io service and can block the UI if performed on main thread.

Warning

You need to first have a protection profile assigned either globally, to your project or to your application installation specifically for the settings retriever to work properly.

Set up the sensitivity.io retrivers with your service details through the class methods of HttpRetriever:

1
2
try HttpRetriever.setHostName("service_hostName")
try HttpRetriever.setApiVersion("service_API_version")

Note

In case your application uses the default sensivitity.io service host name and API version (not custom ones) you can skip this step.

Set up the sensitivity.io retrivers with your credentials through the class methods of HttpRetriever:

1
2
3
try HttpRetriever.setAccountId("your_accountId")
try HttpRetriever.setProjectId("your_projectId")
try HttpRetriever.setAuthenticationKey("your_authKey")

Retrieve your application ID by using the specialized retriever ApplicationIdRetriever:

1
2
3
4
5
6
do {
  let appIdRetriever = try ApplicationIdRetriever.shared()
  try appIdRetriever.retrieveApplicationId()
} catch {
  // failed retrieving application ID
}

Note

Each application ID is unique to the device your application is installed on, if the same credentials and service details were provided.

Retrieve and load license by using LicenseRetriever singleton class:

1
2
3
let licenseRetriever = try LicenseRetriever.shared()
try licenseRetriever.setDestinationFile(licenseFilePath)
try licenseRetriever.retrieveToFileAndNotifyLoader()

or

1
2
let licenseRetriever = try LicenseRetriever.shared()
let license = try licenseRetriever.retrieveAndNotifyLoader()

Use specific module settings retriever classes to instantiate a SettingsRetriever object:

  • ScannerSettingsRetriever for SDS module:

    1
    2
    let settingsRetriever = try ScannerSettingsRetriever()
    let settingsLoader = ScannerSettingsLoader()
    

Retrieve settings and notify loader to automatically load the downloaded settings:

1
2
3
try settingsRetriever.setSettingsLoader(settingsLoader)
try settingsRetriever.setDestinationFile(settingsFilePath)
try settingsRetriever.retrieveToFileAndNotifyLoader()

or

1
2
try settingsRetriever.setSettingsLoader(settingsLoader)
let settings = try settingsRetriever.retrieveAndNotifyLoader()

You can also set an auto retrieve interval for the settings, in case they change frequently, thus they will be downloaded and loaded at the specified interval. The destination file needs to be set for the auto retrieve functionality to work properly, otherwise the retrieval will fail until a destination file is set.

1
2
try settingsRetriever.setDestinationFile(settingsFilePath)
settingsRetriever.autoRetrieveInterval = customAutoRetriveInterval

3. Sensitive Data Scanner (SDS) module

Sensitive Data Scanner module provides the means for scanning data locally, with no need for internet connection.

There are two ways you can perform a scan:

  • by using a single thread scanner, which is represented by Scanner class, or
  • by using a multi thread scanner, which is represented by ScannerExecutor class

So choose whichever fits your requirements best.

Before performing any scans the following steps are required:

  • the module needs to have its resources initialized
  • the license needs to be loaded
  • the settings need to be loaded
  • the scanner needs to be registered with the settings loader containing the loaded settings

Note

When registering a scanner with a scanner settings loader, the already loaded settings will propagate to the new registered scanner. Whenever new settings are loaded with a settings loader that has registered scanners, the new settings will be propagated to the registered scanners.

Warning

Always unregister scanner from settings loader when you no longer need it.

Each type of scanner has 2 types of scan methods:

  • covenience scan where you either perform a full scan or stop at the first encountered threat based on the boolean value you submit for the stopAtFirstThreat parameter
  • scan with threat handler where you define your own stop condition by handling yourself each found threat through your own threat handler

The structure of a threat object is represented by ThreatInfo class. It has the following properties:

  • typeId which represents the threat id
  • typeName which represents the threat name
  • matchedText which represents the text that is considered as threat
  • maskedMatchedText which represents a masked version of the text that is considered as threat
  • threatDescription which represents the description of the threat
  • location which represents the location of the threat

that can be accessed through the throwable setters and getters.

The structure of a threat location is defined through ThreatLocation class and has the following properties:

  • offset which represents the offset where the threat was found, -1 means this information is not available
  • lineNumber which represents the line number where threat was found, -1 means this information is not available
  • fileName which represents the name of the file that was scanned. Non null only when scanning files
  • archiveMember which represents the name of the file where the threat was found inside the scanned archive. Missing if fileName is not an archive
  • surroundingText which represents the surrounding text of the threat
  • inMetadata which represents the location of the found threat as being in metadata or not

that can be accessed through the throwable setters and getters.

Use ThreatHandler class to define your own threat handler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class DemoThreatHandlerData {
  var threatCount: Int = 0
  var stopAtXThreats: Int = 0 // if 0, it scans to the end of file/data/text
}

class DemoThreatHandler: ThreatHandler {

    init(data: DemoThreatHandlerData) {
        super.init(data: data)
    }

    override func handleThreat(_ threat: ThreatInfo) -> sensitivityio_tribool_t {

        guard let demoData = self.data as? DemoThreatHandlerData else {
            return sensitivityio_3b_indeterminate
        }

        // do something with found threat
        print("#\(demoData.threatCount): \(threat)")

        if demoData.stopAtXThreats > 0 && demoData.stopAtXThreats <= demoData.threatCount + 1 {
            // stop condition encountered -> stop scanning
            return sensitivityio_3b_false
        }

        demoData.threatCount += 1

        // stop condition not encountered -> keep on scanning
        return sensitivityio_3b_true

        }
    }
}

3.1 Single thread scanner

Use Scanner class to scan texts, data and files one at a time.

Note

When cancelling a scanner, it will become invalid, thus no more scans can be performed with it. In order to perform a new scan you need to create a new instance of Scanner.

Warning

Cancelling a scanner does not immediately stop the ongoing scan, so, before releasing a scanner, you need to make sure that the scan finished to avoid memory leaks.

Initialize the scanner:

1
let scanner = Scanner()

Register the scanner with the specific settings loader for the SDS module, ScannerSettingsLoader:

1
try settingsLoader.register(scanner)

Whole file convenience scan:

1
let threats = try scanner.scanFile(atPath: filePath, stopAtFirstThreat: false

Convenience scan file and stop at first encountered threat:

1
let threats = try scanner.scanFile(atPath: filePath, stopAtFirstThreat: true)

Scan file with threat handler and stop after encountering 5 threats:

1
2
3
4
5
let threatHandlerData = DemoThreatHandlerData()
threatHandlerData.stopAtXThreats = 5    // stop after encountering 5 threats
let threatHandler = DemoThreatHandler(data: threatHandlerData)

scanner.scanFile(atPath: filePath, threatHandler: threatHandler)

3.2 Multi thread scanner

Use ScannerExecutor class to scan texts, data and files simultaneously through an internal thread pool.

Use ThreadPool class to monitor and configure the scanner executor internal thread pool:

  • get and set the maximum number of threads used by the thread pool
  • get the number of active threads existing in the thread pool
  • get and set threads expiry timeout for the thread pool. The default value is 30000 milliseconds
  • wait until all threads exit and are removed from the thread pool, with the possibility to set a timeout for the wait. Will block your UI if done on main thread

Each scan method of the scanner executor returns an object that conforms to Future protocol, through which you can manipulate and check the state of the scan.

Depending on the type of scan:

  • covenience scan: the returned future object is of type ThreatInfosFuture and has additional methods besides the ones defined by the Future protocol: one to check if scan results are ready and another to get the threats found on scan, the latter will block and wait for the result to become available if results are not immediately ready.
  • scan with threat handler: the returned future object is of type ThreatHandlerFuture and has defined only the Future protocol methods because each threat is handled when found, by the threat handler given to the scan.

The scan methods require you to specify a priority for each scan. High priority scans execute before low priority ones.

Use FutureWatcher class to monitor scan state by registering a future object, that will be periodically checked for its state, and a delegate, that gets notified when a state change was encountered. The interval at which the watchers check for state changes is set to 0.1 seconds by default, but you can customize it to your requirements by using the setTimeInterval class method. The future object must conform to Future protocol while the delegate needs to conform to the FutureWatcherDelegate protocol.

Warning

Before releasing a scanner executor you need to make sure that its corresponding thread pool is empty or wait for it to clear in order to avoid memory leaks.

Initialize the scanner executor:

1
let scannerExecutor = ScannerExecutor()

Register the scanner executor with the specific settings loader for the SDS module, ScannerSettingsLoader:

1
try settingsLoader.register(scannerExecutor)

Whole file convenience scan:

1
let future = scannerExecutor.scanFile(atPath: filePath, stopAtFirstThreat: false, priority: 0)

Convenience scan file and stop at first encountered threat:

1
let future = scannerExecutor.scanFile(atPath: filePath, stopAtFirstThreat: true, priority: 0)

Scan file with threat handler and stop after encountering 5 threats:

1
2
3
4
5
let threatHandlerData = DemoThreatHandlerData()
threatHandlerData.stopAtXThreats = 5    // stop after encountering 5 threats
let threatHandler = DemoThreatHandler(data: threatHandlerData)

let future = scannerExecutor.scanFile(atPath: filePath, threatHandler: threatHandler, priority: 0)

Create a future watcher to monitor a scan:

1
2
3
let futureWatcher = FutureWatcher()
futureWatcher.delegate = self // where self implements FutureWatcherDelegate protocol
futureWatcher.future = future // the future returned by one of the scans of scanner executor

Define your class as adhering to the FutureWatcherDelegate protocol and implement the methods corresponding to the state changes you are interested in, in our case finished:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DemoScannerClass: FutureWatcherDelegate {

    func finished(_ futureWatcher: FutureWatcher) {

        // check what type of scan just finished
        if let threatInfosFuture = futureWatcher.future as? ThreatInfosFuture {
            // a convenience scan just finished
            do {
                // get found threats
                let threats = try threatInfosFuture.result()

                // handle found threats
            } catch {
                // Failed retrieving threats from threatInfosFuture
            }
        } else {
            // a scan with handler just finished

            // threats were already handled by the threatHandler passed to the scan method
        }
    }
}

Cancelling scanner executor pending and ongoing scans is done through the future objects:

  • not monitoring scan state changes (handling only the future objects corresponding to the scans)

    1
    try future.cancel()
    
  • monitoring scan state changes (handling the future watchers monitoring the scans)

    1
    try futureWatcher.future?.cancel()
    

Before releasing the scanner executor wait for thread pool to empty:

1
try scannerExecutor.threadPool().waitForDone()

Warning

Keep in mind that the above method will block and wait until all threads exit and are removed from the thread pool.

In case we have future watchers monitoring the scans, we can make sure that the thread pool is empty by implementing the finished(_) method of the FutureWatcherDelegate protocol and removing the future watcher for which we were notiffied of the finished state, until there are no more watchers left.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class DemoScannerClass {

    private var futureWatchers: Set<FutureWatcher> = Set<FutureWatcher>()

    func scan(_ source: Any) throws {

        let futureWatcher = FutureWatcher()
        futureWatcher.delegate = self

        if let data = source as? Data {
            futureWatcher.future = try scannerExecutor.scanData(data, stopAtFirstThreat: false, fileName: nil, priority: 0)
        }
        else if let url = source as? URL {
            futureWatcher.future = try scannerExecutor.scanFile(atPath: url.path, stopAtFirstThreat: false, priority: 0)
        }
        else if let text = source as? String {
            futureWatcher.future = try scannerExecutor.scanString(text, stopAtFirstThreat: false, priority: 0)
        } else {
            throw POSIXError(.EINVAL)   // invalid argument
        }

        // add it to the monitored array
        futureWatchers.insert(futureWatcher)
    }
}

extension DemoScannerClass: FutureWatcherDelegate {

    func finished(_ futureWatcher: FutureWatcher) {

        // handle finished future watcher depending on your application requirements

        // remove it from the monitored array
        futureWatchers.remove(futureWatcher)

        if futureWatchers.isEmpty {
            // thread pool is empty
        }
    }
}