LEARNING

Getting Started with Circle Guard

An actually secure way to do encrypted email.

Most encrypted emails system claim to be secure since they leverage an external key store to hold the keys that were used to encrypt and decrypt the email.  The problem is, when the message needs to be displayed, the ‘server’ needs to ask the external key store for the decryption key; which means that while the server doesn’t store the key locally, it can still get it. 

How is that secure?

Introducing a better way…Circle Guard.

Since Circle encryption keys are stored on the end user’s device, we can make a better system. 

The flow is nearly the same as other secure email systems save two important differences:

  1. The external key store isn’t on a separate server, but instead on the user’s device
  2. Encryption and decryption happens client side, not on a server.

The code shown here is designed for Outlook 365 but the flow should be basically the same for any other client.  I’m only going to show the parts of the add-in that deal with Circle since this is about using Circle API not how to write an Outlook add-in.

You can see the Outlook Task panel boilerplate project here:

https://docs.microsoft.com/en-us/office/dev/add-ins/quickstarts/outlook-quickstart?tabs=yeomangenerator.

const appKey = "FROM YOUR CONFIGURATION";

const circleId = "ENTER CIRCLE ID HERE";
const topicId = "ENTER TOPIC ID HERE";

Office.onReady(async (info) => {
  if (info.host === Office.HostType.Outlook) {

    let isRunning = await Circle.isServiceRunning();
    if (isRunning)
    {
      let token = await getCircleToken();
      let iniResult = await Circle.initialize(appKey, token);

      if (iniResult && iniResult.Status.Result) {
        console.log("Initialized.");

        // CircleService is running and token is authorized
        let circles = await Circle.enumCircles();
        console.log("enumCircles done.");
      }
      else {
        document.getElementById('runningResult').innerText = 'Failed to get permission.';
        console.log("initialized failed.");
      }
    }
  }
});

// this is just for this testing, the actual token should be coming from your web server
async function getCircleToken() {
    const url = "https://us-central1-serious-app-273117.cloudfunctions.net/circleLicense/api/demo/token?t=1";
    return fetch(url).then((res) => res.json()).then((objJson) => { return objJson.Token; });
}

Next we add a button to the task pane called Encrypt and hooked up this handler

export async function encrypt() {
  // Get a reference to the current message
  var item = Office.context.mailbox.item;
  item.body.getAsync("html", encryptSetbody);  // get the body of the email 
}

async function encryptSetbody(asyncResult) {
  let base64String = utf8_to_b64(asyncResult.value);  // convert the body to base64 so we can encrypt it

  let r = await Circle.secureContents(circleId, topicId, base64String, true); // use the Circle and topic ID you want to encrypt with

  // now generate the new body that includes the URL of the encrypted file
  let emailHtml = "<div style='text-align: center;'>  <p>Click the Cirlce Logo below to view the email.</p> <a href=https://demosupport20220414211325.azurewebsites.net/Home/Render?url="+ r.SecureFileUrl+">  <img src='https://circleauth.prod-mirror.gocircle.ai/demo/files/circle-logo-nocompromise.svg'</a><p><br/>This email is secured by Circle.</p></div>"

  // Get a reference to the current message
  var item = Office.context.mailbox.item;
  try {
    await item.body.setAsync(emailHtml, { coercionType: Office.CoercionType.Html, asyncContext: { var3: 1, var4: 2 } });
  }
  catch(ex) {
    console.log(ex);
  }
}

Later, when we need to render the document on our web server, here’s the JavaScript that we use.  The URL to the encrypted email (from the encrypt phase above) is passed in

async function checkCircleServiceIsRunning() {
    let isRunning = await Circle.isServiceRunning();
    if (isRunning) {
        document.getElementById('status').innerText = 'Requesting permission, please wait.';
    } else {
        document.getElementById('status').innerText = 'Could not find CircleService';
        return false;
    }

    let token = await getCircleToken();
    document.getElementById('status').innerText = 'Initializing Circle Service for Circle Guard functions.';

    let iniResult = await Circle.initialize(appKey, token);
    if (iniResult && iniResult.Status.Result) {
        console.log("Initialized.");
        document.getElementById('status').innerText = 'Initialized';
    }
    return true;
}

async function DecryptAndLoad(circleFileUrl) {
    console.log(fileUrl);
    document.getElementById('status').innerText = 'Circle Service is decrypting message. Please wait...';
    document.getElementById('spinner').visibility = "visible";

    var v = await Circle.getSecureFileContents("", circleFileUrl); // get the decrypted Base64 
    var html = atob(v.FileData); 
    document.open();  // replace the entire document with the email contents.
    document.write(html);
    document.close();
}

async function RenderORama(circleFileUrl)
{
    await checkCircleServiceIsRunning();
    await DecryptAndLoad(circleFileUrl);

}

window.onload = function () {
    RenderORama(fileUrl);
}