Sharing Secure Data Between Apps using Good Dynamics and Xamarin.iOS

Sharing Secure Data Between Apps using Good Dynamics and Xamarin.iOS

The Good Dynamics SDK provides Xamarin developers a wide range of containerized security features that span almost every security need on a mobile device.  In the previous blog post, we used the SDK to implement an encrypted SQLite database and file storage system.  Another key component to mobile security is the ability to share information between applications on the same device securely.  This post will serve as a step-by-step guide to implementing these in Xamarin.iOS complete with code examples.

When two applications are embedded with Good, they use the Good Inter-Container Communication (ICC) system which follows a service consumer-provider model.  This sets up one application as the service provider and one application as the service consumer which allows them to communicate without any security leakage as the information travels between apps.

The general sequence of API calls is as follows:

  1. The consumer application calls the service discovery API.
  2. The consumer application calls sendTo (GDServiceClient).
  3. The GDServiceDidReceiveFrom callback in the provider application is invoked by the ICC system.
  4. The provider application executes any required processing and then calls replyTo (GDService).
  5. The GDServiceClientDidReceiveFrom callback in the consumer is invoked by the ICC system.

The classes, terminology and configuration required are explained with code examples and step-by-step guides for creating a service provider and a service consumer.

The Service Provider

The application that accepts the service provider role has the responsibility for receiving a request, performing some processing, and sending back a response.  The following steps and code example will demonstrate how to give a Good embedded app the service provider role in its ICC relationship to the service consumer app.

To transform your app into a service provider, two classes from the Good Dynamics iOS SDK must be implemented and the app must be correctly configured as a service provider in Good Control.  The three fundamental components are:

  • The GDService class contains the API that sends the ICC responses. This class gets utilized by the GDServiceDelegate and is instantiated as a single object within the app.
  • The GDServiceDelegate listens for and handles requests for the service. Create a class that implements this one that has a reference to your service. This custom delegate will be set to GDService.Delegate.
  • Register the ICC URL type on the device with Good-Control. This configures the application as a secure service provider through Good.

There are two types of service requests: foreground and background.  Foreground requests to the service providing app navigate the user away from the requesting app and bring the servicing app in front of the user.  Background requests perform the request while still keeping the requesting app in front of the user.  Foreground requests are used when additional input is required from the user such as a special web browser or document editor.

GDService

To demonstrate this feature, we will be taking a look at one of Good’s sample apps called GreetingsServer which is a simple service provider application.

The ServiceController is a class that handles the GDService implementation.  The API methods that send ICC responses are called by the methods in this class.

public class ServiceController
{
    public const string kMethodNotImplementedDescription = "The requested method is not implemented.";
    public const string kServiceNotImplementedDescription = "The requested service is not implemented.";
    public const string kDateAndTimeServiceId = "com.gd.example.services.dateandtime";
    public const string kGreetingsServiceId = "com.good.gd.example.services.greetings";

    public GDService GDService {
        get;
        set;
    }

    private const int kGreetingsServiceErrorNotImplemented = -1;
    private const string kGreetingsServiceErrorNotImplementedDescription = "The requested service, version, and method is not implemented.";

    public ServiceController ()
    {
        GDService = new GDService ();
        GDService.Delegate = new GreetingsServerGDServiceDelegate (this);
    }

    public bool ConsumeFrontRequestService (string serviceID, string application, string method, string version)
    {
 
    }

    public void SendErrorTo (string application, NSError error)
    {
 
    }
}

 

In the ServiceController constructor, we instantiate the GDService class and create a local reference later usage.

Next, we set our custom GDServiceDelegate to the delegate on GDService.  Doing so registers the custom delegate as a listener for incoming request to the GDService.

The ConsumeFrontRequestService method is called when an incoming request wants the servicing app to be brought into the foreground.  The SendErrorTo method handles all error responses that come from the app and sends them back.  Both of these will be explained in further detail later.

There are a few important parameters here.

  • The serviceID parameter is a string that describes the identifier of the service.  It will be of the form GoodApplicationId.Services.ServiceName where GoodApplicationId is the Good Entitlement ID (which should match your bundle identifier).  Services is just the name of the service.  ServiceName is the name of the service method.
  • The Application parameter is the identifier for the requesting application.  Typically, this value is only used when sending back a response to the original application that sent the request.
  • The method parameter determines what method in the service to use.  If the service method is specified in the serviceID, then this parameter can be used to determine which submethod to use, or be used in a conditional statement to determine what specific functionality needs to happen.
  • The version parameter must match the GD Entitlement Version of the service providing Good app.

GDServiceDelegate

The next important piece is the GDServiceDelegate.  We must make a custom class that extends it.

public class GreetingsServerGDServiceDelegate : GDServiceDelegate
{
    public ServiceController ServiceController {
        get;
        private set;
    }

    public GreetingsServerGDServiceDelegate (ServiceController controller)
    {
        ServiceController = controller;
    }

    public override async void DidReceiveFrom (string application, string service, string version, string method, NSObject parameters, NSObject[] attachments, string requestID)
    {
        await ProcessRequestForApplication (application, service, version, method, parameters, attachments, requestID);
    }

    async Task ProcessRequestForApplication (string application, string service, string version, string method, Foundation.NSObject parameters, NSObject[] attachments, string requestID)
    {
        bool requestProcessed = false;
        NSError goodError = null;

        if (!ServiceController.ConsumeFrontRequestService (service, application, method, version)) {
            if (String.Equals (service, "com.good.gd.example.services.greetings")) {
                requestProcessed = ProcessGreetingsService (application, service, version, method, parameters, attachments, requestID, out goodError);
            } else if (String.Equals (service, "com.gd.example.services.dateandtime")) {
                requestProcessed = ProcessDateAndTimeServiceRequest (application, service, version, method, parameters, attachments, requestID, out goodError);
            }

            if (!requestProcessed && goodError == null) {
                NSDictionary errorDetail = new NSDictionary ();
                errorDetail.SetValueForKey (new NSString (ServiceController.kServiceNotImplementedDescription), NSError.LocalizedDescriptionKey);
                NSError serviceError = new NSError (ICCErrorConstants.GDServicesErrorDomain, ICCErrorConstants.GDServicesErrorServiceNotFound, errorDetail);
                ServiceController.SendErrorTo (application, serviceError);
            }
        }
    }

    private bool ProcessGreetingsService (string application, string service, string version, string method, Foundation.NSObject parameters, NSObject[] attachments, string requestID, out NSError goodError)
    {
 
    }

    private bool ProcessDateAndTimeServiceRequest (string application, string service, string version, string method, Foundation.NSObject parameters, NSObject[] attachments, string requestID, out NSError goodError)
    {
 
    }
}

Our custom GDServiceDelegate called GreetingsServerGDServiceDelegate has a property for the ServiceController class that is set in the constructor.  Because the delegate is responsible for listening for and handling service requests, the ServiceController’s methods and GDService methods get called from the delegate.  The DidReceiveFrom method is a parent method that listens for the request data.  We override it to handle the request using our own logic.

In ProcessRequestForApplication, there are three possible outcomes that happen to any request that gets sent through.

The first step is that we check if the request is a foreground request which gets handled in the ServiceController:

public bool ConsumeFrontRequestService (string serviceID, string application, string method, string version)
{
    if (serviceID.Equals (GoodDynamics.ICCMiscConstants.GDFrontRequestService) && version.Equals ("1.0.0.0")) {
        if (method.Equals (GoodDynamics.ICCMiscConstants.GDFrontRequestMethod)) {
            NSError error = null;
            GDService.BringToFront (application, out error);
        } else {
            NSDictionary errorDetail = new NSDictionary ();
            errorDetail.SetValueForKey (new NSString (kMethodNotImplementedDescription), NSError.LocalizedDescriptionKey);
            NSError serviceError = new NSError (ICCErrorConstants.GDServicesErrorDomain, ICCErrorConstants.GDServicesErrorMethodNotFound, errorDetail); 
            SendErrorTo (application, serviceError);
        }
        return true;
    }
    return false;
}

Outside some standard error checking, the key line of code is GDService.BringToFront().  The service requesting app specifies via the method parameter whether to bring the service providing app to the foreground or not by using the string constant GoodDynamics.ICCMiscConstants.GDFrontRequestService.

The second step in ProcessRequestForApplication is to determine which background service to invoke since the service request did not specify a foreground request.  This is done by assigning the request’s serviceID parameter to a method via conditional logic.

The final step is to send an error back to the requesting application if it didn’t request a foreground service or an identifiable background one.  The error sending is done the ServiceController:

public void SendErrorTo (string application, NSError error)
{
    NSError goodError = null;
    bool didSendErrorResponse = GDService.ReplyTo (application, error, GDTForegroundOption.EPreferPeerInForeground, null, null, out goodError);
    if (!didSendErrorResponse) {
        if (goodError != null) {
            UIAlertView alert = new UIAlertView ("Error", goodError.LocalizedDescription, null, "OK", null);
            alert.Show ();
        }
    }
}

The notable line of code here is GDService.ReplyTo() which is the standard GDService method that sends responses back to the requesting application.

The final component to demonstrate is an example of a service method.  In this case, we will look at ProcessDateAndTimeServiceRequest():

private bool ProcessDateAndTimeServiceRequest (string application, string service, string version, string method,
        Foundation.NSObject parameters, NSObject[] attachments, string requestID, out NSError goodError)
{
    goodError = null;
    bool didSendResponse = false;
  
    if (String.Equals (version, "1.0.0")) {
        String dateString = DateTime.Now.ToString ("g");
        didSendResponse = GDService.ReplyTo (application, new NSString (dateString), GDTForegroundOption.EPreferPeerInForeground, null, requestID, out goodError);
    }

    return didSendResponse;
}

Aside from confirming the version number, all this does is send back the current date.  Much like how SeriveController uses GDService.ReplyTo() to send back an error, it is also used to send back a successful response.  Any response can be sent since the parameter expects an object.  You can also send back attachments which are a list of file paths that indicate a location in the GDFileSystem.  The option parameter allows you to specify 1 of 4 ways to send the response back:

  • GDTForegroundOption specifying the foreground execution preference after delivery of the response
  • GDEPreferPeerInForeground for the consumer application being in the foreground
  • GDEPreferMeInForeground for this application being in the foreground
  • GDENoForegroundPreference to specify that there is no preference

 

Register with Good Control

Now that the GDService is fully implemented, it’s time to register it as a service provider with Good Control.  To register a new service in Good Control, follow these steps:

  1. Click Manage Services in the main navigation. A list of all application services currently registered with Good Control are displayed.
  2. In the upper right, click the + icon. You will be taken to the “Add Service” screen.
  3. Enter the required information for the new service. You can also specify optional information such as the description of the serviceand version or the interface format if the service is provided by a server-based application.
  4. On completion, click Add Service. You are directed to the screen to manage the new service.

A few considerations when registering a service:

  • Service You must specify whether the serviceis offered by a GD mobile application or by an application on a server.
  • Name of the service.
  • ID for the service. The ID is a unique string in reverse DNS notation and must consist of all lowercase letters separated by dots (e.g., com.good.service.print). Make sure this matches the app’s GD Entitlement ID.
  • Version identifier of the service. Versions consist of digits only, and are period delimited when applicable to show build numbers or other information. For example, valid version numbers include 2, 3, 2.3.0, and 2.3.0.1. Leading zeros are not allowed, so 2.03is not a valid version number.  Make sure this matches the app’s GD Version ID
  • Servicedefinition in JSON.

After this has been completed, your Good app is fully configured as a service and ready to respond to requesting applications.  In the next section, we will go over the steps required for another Good app to send a request to the service you just set up.

 

The Service Consumer

The service consumer application is the Good app that sends a request the other Good app that is configured as a service.  The following steps and code example will demonstrate how to give a Good embedded app the service consumer role in its ICC relationship to the service providing app.

The responsibilities for the service consumer are simple compared to the service provider.  To be a service consumer, all the app has to do is know how to send a request to a service provider and listen for the response.  To transform your app into a service consumer, two classes from the Good Dynamics SDK must be implemented.  The two fundamental components are:

  • The GDServiceClient class contains the API that sends the ICC requests and receives the ICC responses. This class gets utilized by the GDServiceClientDelegate and is instantiated as a single object within the app.
  • The GDServiceClientDelegate sends and handles responses from the service. Create a class that implements this one that has a reference to your service.  This custom delegate will be set to GDServiceClient.Delegate.

GDServiceClient

To demonstrate this feature, we will be taking a look at one of Good’s sample apps called GreetingsClient which is a simple service consuming application.

The ServiceController is a class that handles the GDServiceClient implementation.  The API methods that send ICC requests and listen for responses are called by the methods in this class:

public class ServiceControllerDelegate
{
    public void ShowAlert (string title, string reply)
    {
        UIAlertView view = new UIAlertView (title, reply, null, "OK", null);
        view.Show ();
    }
}

public class ServiceController
{
    public const string kMethodNotImplementedDescription = "The requested method is not implemented.";
    public const string kServiceNotImplementedDescription = "The requested service is not implemented.";
    public const string kDateAndTimeServiceId = "com.gd.example.services.dateandtime";
    public const string kGreetingsServiceId = "com.good.gd.example.services.greetings";

    public enum ClientRequestType
    {
        GreetMe,
        BringServiceAppToFront,
        SendFiles,
        GetDateAndTime
    }

    public ServiceControllerDelegate Delegate {
        get;
        set;
    }

    public GDServiceClient GoodServiceClient {
        get;
        set;
    }

    public ServiceController ()
    {
        Delegate = new ServiceControllerDelegate ();
        GoodServiceClient = new GDServiceClient ();
        GoodServiceClient.Delegate = new GreetingsClientGDServiceClientDelegate (this);
    }
 
    public bool SendRequest (out NSError error, ClientRequestType type, string appId)
    {
        bool result = false;
        switch (type) {
        case ClientRequestType.GreetMe:
            result = SendGreetMeRequest (out error, appId);
            break;
        case ClientRequestType.BringServiceAppToFront:
            result = BringServiceAppToFront (out error, appId);
            break;
        case ClientRequestType.SendFiles:
            result = SendFilesRequest (out error, appId);
            break;
        case ClientRequestType.GetDateAndTime:
            result = SendGetDateAndTimeRequest (out error, appId);
            break;
        default:
            error = null;
            break;
    }

    return result;
    }

    public bool SendGreetMeRequest (out NSError error, string appId)
    {
        string requestId;
        return GDServiceClient.SendTo (appId, kGreetingsServiceId, "1.0.0", "greetMe", null, null,
            GDTForegroundOption.EPreferMeInForeground, out requestId, out error);
    }

    public bool BringServiceAppToFront (out NSError error, string appId)
    {
        return GDServiceClient.BringToFront (appId, out error);
    }

    public bool SendFilesRequest (out NSError error, string appId)
    {

    }

    public bool SendGetDateAndTimeRequest (out NSError error, string appId)
    {
        string requestId;
        return GDServiceClient.SendTo (appId, kDateAndTimeServiceId, "1.0.0", "getDateAndTime", null,
            null, GDTForegroundOption.EPreferMeInForeground, out requestId, out error);
    }
}

The GDServiceClient implementation of the ServiceController is similar to the GDService implementation.  We instantiate the GDServiceClient in the constructor and set the delegate to the custom one we will create.  Each type of service call is assigned to its own method that takes in the appId parameter which tells the application which service to hit.  The appId is structured the same way as the appId in the GDService guide.

The ServiceController’s SendRequest method is invoked from the button press events in the RootViewController, but it can be called anywhere in the app at the developer’s discretion.  This method determines which service to call based on an enum the developer specifies.

The methods SendGreetMeRequest, BringServiceAppToFront, SendFilesRequest, and SendGetDateAndTimeRequest each invoke the GDServiceClient’s API methods that actually send the request to the service.

There are three methods that the GDServiceClient provides:

  • SendTo: This is the method that actually sends the request. You will be using this most of the time
  • CancelRequest: Cancels a given request that was sent by the application. This method accepts a requestId parameter which gets generated by the SendTo
  • BringToFront: This method brings the service providing app to the front. This should be not confused with the GDService.BringToFront.  The difference here is that GDServiceClient.BringToFront only accepts arguments for the appId parameter and nothing to specify which service method to call.  This means that this method simply brings the service providing app to the foreground without instructing it to call a method.  The GDService.BringToFront method accepts serviceID, method, and version parameters which can instruct the service provider app to execute a method along with bringing it to the foreground.

The following is the full code sample for the SendFileRequest method.  It shows how to prepare files to send in a service request:

public bool SendFilesRequest (out NSError error, string appId)
{
    string filename1 = "first.txt";
    NSData data1 = new NSString ("This is first.txt, the first test file to send.").Encode (NSStringEncoding.UTF8);
    NSError file1Error = null;
    bool written = GDFileSystem.WriteToFile (data1, filename1, out file1Error);

    if (!written) {
        Debug.WriteLine (String.Format ("Error writing to {0}", filename1));
        error = null;
        return false;
    }

    string filename2 = "second.txt";
    NSData data2 = new NSString ("This is second.txt, the second test file to send.").Encode (NSStringEncoding.UTF8);
    NSError file2Error = null;
    written = GDFileSystem.WriteToFile (data2, filename2, out file2Error);

    if (!written) {
        Debug.WriteLine (String.Format ("Error writing to {0}", filename2));
        error = null;
        return false;
    }

    NSObject[] files = new NSObject[2]{ new NSString (filename1), new NSString (filename2) };
    string requestId;
    return GDServiceClient.SendTo (appId, kGreetingsServiceId, "1.0.0", "sendFiles", null, files, GDTForegroundOption.EPreferPeerInForeground, out requestId, out error);
}

 

GDServiceClientDelegate

The next important piece is the GDServiceClientDelegate.  We make a custom class that extends it.

public class GreetingsClientGDServiceClientDelegate : GDServiceClientDelegate
{
    public ServiceController ServiceController {
        get;
        private set;
    }
  
    public GreetingsClientGDServiceClientDelegate (ServiceController controller)
    {
        ServiceController = controller;
    }

    public override void DidFinishSendingTo (string application, NSObject[] attachments, Foundation.NSObject parameters, string requestID)
    {
    }

    public override void DidRecieveFrom (string application, Foundation.NSObject parameters, NSObject[] attachments, string requestID)
    {
        if(parameters != null && (parameters.GetType() == typeof(NSString) || parameters.GetType() == typeof(NSMutableString) || parameters.GetType() == typeof(NSError)))
        {
            NSString message = null;
            message = parameters as NSString;
            string title = "Success!";
            if (message == null) {
                title = "Error!";
                message = (NSString)((NSError)parameters).ValueForKey (NSError.LocalizedDescriptionKey);
            }
            ServiceController.Delegate.ShowAlert (title, message);
        }
    }
}

 

This delegate has a similar role to the GDServiceDelegate but handles service requesting and response listening methods instead.

  • The application parameter is consistent with the rest of the GDService API.
  • The attachments parameter is a list of file locations in GDFileSystem that contain the documents that are relevant to the service.
  • The parameters parameter contains the message body from the response
  • The requestID parameter is a unique identifier for the service request that was generated when it was sent.

The methods on the delegate are:

  • DidFinishSendingTo: This is called once the service request has been sent.
  • DidReceiveFrom: This is called when the service has returned with a response.  Every response will invoke this method so you will typically add some conditional logic that determines what to do with the response.
  • DidStartSendingTo: This gets called when a service request with a file attachment starts transferring data to the recipient device.  This should be used, for example, when you wish to display a sending status bar in the user interface.  It does not make sense to use this for requests without attachments because the time between the request starting to send and the request being sent is negligible.
  • WillStartReceivingFrom:  Much like DidStartSendingTo, this gets called when a service response comes back with file data and it begins to transfer it to the service consuming app.

This wraps up the guide on how to implement secure app-to-app communication using Good Dynamics.  Aside from this, Good Dynamics brings a wealth of security features to your mobile sppd.  Stay tuned for more feature write-ups.  This post is part of a series of building enterprise mobile applications with Good Dynamics & Xamarin. To read more click here.

Our mobility consulting professionals have extensive experience developing enterprise and consumer mobile apps using Xamarin on the Apple iOS, Android, and Windows Phone platforms. We’ve built numerous apps utilizing Good Dynamics.  Contact us for more information on how we can work with you to build highly secure, cross-platform mobile applications.

Your email address will not be published. Required fields are marked *

Phone: 312-602-4000
Email: marketing@westmonroepartners.com
222 W. Adams
Chicago, IL 60606
Show Buttons
Share On Facebook
Share On Twitter
Share on LinkedIn
Hide Buttons