@trellisfw/masklink

Trellis Framework Mask&Link Library

Usage no npm install needed!

<script type="module">
  import trellisfwMasklink from 'https://cdn.skypack.dev/@trellisfw/masklink';
</script>

README

trellisfw-masklink

Library for performing Trellis Mask & Link operations. See below for an overview.

Basic Usage

import ml from '@trellisfw/masklink'
// You can make a private JWK key with https://github.com/oada/oada-certs
import privateJWK from './private_jwk.json'

let url = 'https://my.trellis.domain/resources/123';
const paths = [ 'organization/location', 'scope/organization/location' ];
const token = 'mydummytoken';
const signer = { name: "Sam I Am", url: "https://domain.com" };

const maskedResourceid = await ml.maskAndSignRemoteResourceAsNewResource({url, privateJWK, signer, token, paths});
console.log('The new masked resourceid at the remote URL is: ', maskedResourceid);


url = `https://my.trellis.domain/${maskedResourceid}` 
const { trusted, unchanged, valid, match, original, details } = await ml.verifyRemoteResource({url, token});
console.log('The remote resource was trusted? ',trusted, ', unchanged? ', unchanged, ', valid? ', valid, ', match? ', match);

Overview

When you have information in a JSON document that you do not want to share with someone, but you need to share the rest of the document, you can replace the sensitive information with a auditable trellis-mask.

In addition to replacing the original information, you can sign the document as an added layer of trust. The signature contains information about which keys were masked, allowing anyone with access to the unmasked original to verify both the signatures and the masks whenever an audit is necessary.

For example, if this is your unmasked document:

{
  phone: "999-999-9999",
  location: { // This location is sensitive information
    street_address: "123 Nowhere Lane",
    city: "Nowhere",
    state: "FL",
    zip: "99999"
  }
}

And you want to mask the location key, the masked version would look like:

{ 
  phone: "999-99-9999",
  location: {

    // See the contents here have been replaced by an object with a hash and a link:
    "trellis-mask": {
      version: "1.0",
      hashinfo: {
        alg: "SHA256",
        hash: "02fjkdofj213oikjwdo0fi2jfpiwjsdc029u3f0923uj23oiesls",
      },
      "url": "https://some.trellis.domain/resources/the_original_resourceid/location",
      "nonceurl": "https://some.trellis.domain/resource/the_original_resourceid/_meta/nonce",
    },
  },

  // A trellisfw-signature can be applied during masking to add another layer of trust
  signatures: [
    "ejfkdo9fk234.k0f2jik2lfjafwe9oifjlwjhqi3fwlakefjaowkefu02ijfklsafjwasdf.dkfj23",
  ],
}

API for Masked Objects (not full documents)

mask({ original, url, nonce, nonceurl }) synchronous

  • original: required: the object to be hashed and masked
  • url: required: the remote URL where this object would be found at a Trellis domain, including the path to this object inside a resource.
  • nonceurl: required: the URL where the nonce can be retrieved by someone trying to validate this hash later.
  • nonce: optional: if you don't pass a nonce, one will be created for you. Note you have to save it somewhere... Note this function makes no outside requests, it only creates the mask.

Returns { nonce, nonceurl, mask }

const { nonce, nonceurl, mask } = mask({original, url, nonceurl});
console.log('Mask = ', mask);
// { trellis-mask: { version: "1.0", hashinfo: { alg: "SHA256", hash: "02ijd0fijk2lfwd" }, nonceurl, url } }

verify({mask, original, nonce}) synchronous

  • mask: required: the masked object to be verified
  • original: required: the original unmasked object to hash and compare with the mask
  • nonce: required: the nonce used to create the original mask Note: this function makes no outside requests, it only validates based on what it is given.

Returns { valid, match, details }

  • valid: true|false: true if mask, original, and nonce have valid forms, but says nothing about whether they match.
  • match: true|false: true if hash inside mask matches original w/ nonce.
  • details: array: array of strings about the matching process to aid in debugging

async verifyRemote({mask, token, connection})

Given a mask, retrieve the original at mask.url and the nonce at mask.nonceurl and then pass to verify

  • mask: required: the original masked object to be validated against it's internal remote URL's
  • token: optional: the token to use when connecting to the remote URL
  • connection: optional: a pre-existing oada-cache connection to the remote URL NOTE: you must pass either a connection or a token so the function can make the necessary requests.

Returns { valid, match, original, nonce, details }

  • valid: true|false: true if mask, original, and nonce have valid forms, but says nothing about whether they match.
  • match: true|false: true if hash inside mask matches original w/ nonce.
  • original: the original unmasked object that was retrieved from mask.url
  • nonce: the nonce that was retrieved from mask.nonceurl
  • details: array: array of strings about the matching process to aid in debugging

API for Full Documents Containing Masks

maskResource({ resource, urlToResource, paths, nonce, nonceurl }) synchronous

Given an entire JSON document, use the list of json-pointer paths to mask some of its contents.

  • resource required: the original resource to be masked
  • urlToResource required: where this resource was stored, to be used in the mask url's
  • paths required: array of json-pointer paths to mask within this resource (i.e. [ 'organization/location' ])
  • nonce optional: nonce to use in hashing. If you don't pass it, one is created.
  • nonceurl optional: where the nonce will be stored. Assumed <urlToResource>/_meta/nonce if not passed. NOTE: this function is entirely local, it makes no outside requests.

Returns { nonce, resource, nonceurl }

  • nonce: the nonce used (either passed or created)
  • resource: the final resource after masking the paths
  • nonceurl: the nonceurl to store the nonce (either passed or created)

async signResource({resource, privateJWK, header, signer, paths})

Creates a mask-type signature on a resource using the trellisfw-signatures.

  • resource: A resource that has already had masks applied to it that correspond to paths.
  • privateJWK: A JWK that is the private key used to create the JWT signature
  • header optional: any additional headers to pass to trellisfw-signatures
  • signer optional: Object describing who is signing. Looks like { name: "The Signing Company", url: "https://domain.com" }
  • paths optional: Array of json-pointer paths that were masked in resource corresponding with this signature.

Returns resource (a new copy of the resource with the signature added)

async maskRemoteResourceAsNewResource({url, paths, token, connection, signatureCallback})

  • url required: The URL where the original resource to mask can be found
  • paths required: List of json-pointer paths into the original resource that should be signed.
  • token or connection required: Pass either a token or an oada-cache connection to use in getting the original and putting back the mask.
  • signatureCallback optional: If you want to apply a signature after masking, pass it here and it will be called after masking before creating the new masked resource at the remote URL.

Returns newResourceid (the ID of the new resource on the remote cloud, looks like resources/02ikefj092jlkdss)

async maskAndSignRemoteResourceAsNewResource({url, privateJWK, signer, token, connection, paths})

Given a remote URL, make a masked copy, sign it with the given key, and put it back to the remote cloud. Mostly a wrapper for maskRemoteResourceAsNewResource and signResource. Refer to signResource and maskRemoteResourceAsNewResource for an explanation of the parameters.

Returns newResourceid (the ID of the new resource on the remote cloud, looks like resources/02ikefj092jlkdss)

async verifyRemoteResource({url, token, connection})

Given a remote URL for a masked resource, get it, reconstruct it from the signatures, and verify every masked object along the way. Note this will verify all signatures present on the document, not just the last one.

  • url required: URL of the remote masked resource that you want to verify
  • token or connection required: Pass either a token or an oada-cache connection to use in getting the mask and the original. Note that currently it uses the same for both the mask and original. Future feature add would be to allow those to be different.

Returns { trusted, unchanged, valid, match, original, details }

  • trusted: true|false: Same as the trusted return value from trellisfw-signatures. Indicates that the signature came from a key that is represented on the trusted list.
  • unchanged: true|false: true if the reconstruction matches the signatures (i.e. it was unchanged since signing)
  • valid: true|false: true if all the signtures and all the masks they reference in the document have valid forms. Does not tell you if they are unchanged or if the masks match the original.
  • match: true|false: true if all masks mentioned in signatures match their originals
  • original: the full original resource, reconstructed from the signatures and mask originals
  • details: array of strings with messages about the verification process, useful for debugging.

Exposed Helper Functions

isMask(obj) synchronous

Returns true if obj has all the appropriate keys to be a trellis-mask.

domainForMask(mask) synchronous

Returns the domain portion of the url found within the mask. Note that it ignores the nonceurl.

findAllMaskPathsInResource(resource) synchronous

Returns an array of json-pointer strings, containing every path whose value returns true for isMask() within the passed resource

const paths = findAllMaskPathsInResource(resource);
console.log(paths);
// [ 'organization/location', 'scope/organization/location' ]