CEP: In-Circuit Debugging of a .zxp File

It’s not common knowledge, but you can package a CEP extension into a .zxp file and debug it ‘in-circuit’, without needing to set the CEP debug flag.

My trick is to not use the ZXPSignCmd to package up the .zxp file.

Last time I checked, as far as I could tell, the ZXPSignCmd app takes notice, and will omit the .debug file and will refuse to add it.

However, my new PluginInstaller is able to inject a .debug file into a packaged .zxp.

As it so turns out, that makes the installed extension debuggable, without setting the CEP debug flag.

You would not want to do this in a release version of your extension – but when it comes to diagnosing and debugging difficult-to-catch bugs on someone else’s workstation, this can be invaluable.

Below a little cookbook to demonstrate how you can go about this.

I’ve also made a ‘live video’ about it. The quality of the video is what it is, I think you can tell I am not interested in becoming a ‘Youtuber’. My main concern is the content, not the presentation.


First, download the CEPSparker project.


Click the green Code button and then click the option Download ZIP.

CEPSparker is a framework I made to help me generate ‘starter’ projects for CEP.

Important to note: CEPSparker is not a CEP extension project. It is a precursor to a CEP extension project.

`After downloading, if you’re on a Mac, you MUST right-click the initialSetupConfigApp.command file, select Open, then allow it to run by clicking the Open button.

This de-quarantines the other .command files so they can be double-clicked.

On Windows, expect some blue warning screens (click More Info and run anyway).

You will need to run the .bat files from the command line in an elevated Administrator CMD session; you can double-click sudo.bat to start such a session.

Next, double-click the SparkerConfig or SparkerConfig.exe application. For the demo, change the STARTERCODE dropdown menu and pick IFrameUIServer.

Click Generate.

This will generate a sample CEP project that has a ‘split personality’. The CEP panel is featureless, and only contains an <iframe>.

The content of this <iframe> is pulled from a web server.

The sample code demonstrates how such server-provided code can drive a user-interface inside a CEP panel, and interact with the local host app.

For the demo, we use a small local Node.js application to play the role of the ‘remote server’. The Generate will have created a lot of folders in the CEP project folder.

One of them is IFrameUIServer

Inside you find a little file server.js. You need to have Node.js installed; Google around if you don’t have it installed yet.

All you need to do is open a command line and navigate into this folder, then run

node server.js

and that will run a tiny web server on http://localhost:8001

Next, we will tweak the CRDT manifest for this CEP panel. The CRDT manifest (a file called CRDT_manifest.json) is used by PluginInstaller to determine what it needs to do to package the CEP panel.

Open CRDT_manifest.json in a text editor and change it so it reads:

"injectDebugFile": true,

You can leave the "productCode" entry set to CEPSparkerTest, for the sake of argument – we need this code for creating a product entry in PluginInstaller.

This tell the PluginInstaller to copy the debug file from the CEP Project into the ZXP and rename it to .debug.

If you inspect the debug file in a text editor, you see it configures the debugger to use port 8888:

<?xml version="1.0" encoding="UTF-8"?>
    <Extension Id="com.rorohiko.cepsparker.samplepanel">
            <Host Name="IDSN" Port="8888" /> 

Now run PluginInstaller and switch to developer mode (File – PluginInstaller ModePublisher/Developer). If you don’t have a registered account yet, you need to create one first: after switching to Developer mode, switch to the Accounts window.

Registration is free, but needs to be approved.

To register, you need to click New and fill in a terse bit of detail.

Once you click Register you’ll need to wait for a confirmation email, and after that, I will need to handle your registration request, and I will need to approve it first; if I don’t personally know you I’ll reach out via email.

Once you have a registered developer account, you can create a product entry in the Products window.

For this test, you can just fill in some random details. The only thing that needs to match is the product code which is CEPSparkerTest (unless you changed the "productCode" in CRDT_manifest.json – these names need to match for PluginInstaller to find the manifest).

Once you complete the new product entry, you can click Package… and navigate to the CEP project folder.

Once the packaging completes, you’ll find a .zxp file in the build subfolder.

For the demo, you can now install this .zxp file using a standard CEP installer (for example Anastasiy’s Extension Manager).

Make sure the Adobe CEP debug flag is turned off before trying this out if you want to verify that the debug flag has no influence on the debuggability of this .zxp

With the IFrameUIServer running, start InDesign and open the panel.

The panel should populate and show a UI that has a New Document button. This whole UI is served from the remote web server – if you look at your Node.js terminal window, you’ll see the http requests fly past.

Click New Document to create a new document.

Now start Chrome and access http://localhost:8888 – this will show you the familiar debug interface with two available sessions: one for the panel itself and a second one for the user interface inside the <iframe> on the panel.

Reach out to me if you find this kind of stuff helpful or interesting. I can help you kickstart your automation project and greatly reduce the time needed. In most of my projects, my role is to be a temporary member of the team, and help build the software, by managing, training, designing and coding, as needed.


Creative Developer Tools – status update

I’ve been working hard on Creative Developer Tools (CRDT).

My current focus is to implement a unified packager/installer/license management for third party Adobe scripts/extensions/plug-ins.

PluginInstaller handles the whole developer-to-user chain: packaging, downloading, installing, removing, updating…

I’ve just finished adding support for CEP. PluginInstaller now handles plain ExtendScript as well as CEP extensions. Next up is UXP.

PluginInstaller is part of the growing suite of Creative Developer Tools.

Unified Packager/Installer

PluginInstaller handles both packaging (developer side) and installing (user side) of third party enhancements to the Adobe Creative Cloud apps.

I’ve just released a new version of the PluginInstaller which combines support for plain ExtendScript (.jsx) as well as CEP panels (.zxp).

My plan is to further extend PluginInstaller so the same packager/installer will also handle UXP/UXPScript and C++ plug-ins.

PluginInstaller uses a unified package format (.tpkg) which combines a number of features:

  • Code signing
  • Code encryption
  • Licensing and activation
  • JSON meta-information in the .tpkg file header
  • Support multiple types of solutions (.jsx, .zxp…)

PluginInstaller can be downloaded here:


CEP: no more ZXPSignCmd

I’ve managed to fully integrate all the steps needed for .zxp code signing into CRDT/PluginInstaller.

As we all know, Adobe’s .zxp code signing is both required and ineffective – i.e. it’s just security theater. Just an additional hurdle that developers have to jump.

When packaging a CEP extension using PluginInstaller, it will automatically create and manage the necessary self-signed signing certificates during the packaging operation.

Separate of the Adobe code signing setup, CRDT/PluginInstaller implements a more generic code signing mechanism. This mechanism can handle all kind of project types, including plain ExtendScript script projects.

When PluginInstaller manages a CEP extension, it transparently manages two distinct ‘layers’ of code signing: one as imposed by Adobe, the second layer is handled by CRDT.

PluginInstaller will build both a standard .zxp as well as a .tpkg file when packaging a CEP extension.

The .zxp file is a courtesy file which is not needed nor used by CRDT. It allows the developer to opt for using existing installer tools (e.g. Anastasiy’s Extension Manager) instead of PluginInstaller.

Such .zxp is not protected by CRDT code signing, but it can still have access to the protective CRDT code encryption features as long as the CRDT_ES runtime is included into the package.

The PluginInstaller also handles downloading of .tpkg and handles management of activation files for commercial solutions that need licensing/demo configurations.

CRDT Encryption

CRDT uses two levels of encryption, for two different purposes: an outer level (public/private key) handles code signing, and an inner level (AES-256) handles code protection.

Code signing is there to protect the user: they can verify that the code they are about to run was created by someone they can trust.

Code encryption is there to protect the developer: it blocks malicious users from inspecting and copying the crucial code magic that makes a software solution work.

.tpkg files

.tpkg files are self-identifying. They are structured so they have a human/computer readable JSON header which contains useful meta-info about their content.

This also allows the use of partial downloads: we can download the first 1024 bytes of any .tpkg file and figure out what they are without having to download the whole file.

CRDT Code Signing

When a developer registers a developer account in PluginManager, it will use OpenSSL to automatically generate a public/private key pair and publish the public key in tgrg.net online registry. PluginInstaller handles key generation automatically and there is no cost to creating these keys.

The private key is only stored on the developer’s workstation and used to encrypt anything that gets packaged by the developer.

A package will only work when it is decrypted with the matching public developer key as retrieved from the registry.

The approach was chosen to avoid forcing developers to deal with expensive code-signing certificates.

When a developer creates a .tpkg, the file is encrypted using the developer’s private key.

Then, when the user downloads and installs the .tpkg, the file is decrypted using the developer’s public key as retrieved from the registry.

That way the user can make sure they know who the .tpkg was created by.

Code encryption

When a .tpkg file contains .jsx files, they are encrypted using AES-256.

CRDT_ES comes with a binary runtime in a .dylib/.dll. This runtime is loaded into the ExtendScript engine inside the host app, and it handles on-the-fly decryption and execution. This works for plain .jsx as well as for .jsx embedded inside a CEP solution/.zxp.

CRDT currently does not transparently handle encryption of other file types than .jsx (e.g. .js).

Encryption of .js files (as opposed to .jsx files) would currently not be a strong deterrent for hackers because the decrypted .js code could be inspected by way of a debugger, simply by putting a breakpoint after the decryption call.

.jsx is different: CRDT can execute the encrypted code straight from the CRDT runtime, which is binary C++ code, without making the code easily inspectable in an ExtendScript debugger. By judiciously using closures, the code can be made inaccessible to would-be snoopers that know how to use the ExtendScript debugger.

For .js I cannot do that (yet). I do plan to change this later, but I need to do some research and find Adobe documentation: I need to be able to embed and call a binary runtime from the embedded Node.js or UXP engines. I am eagerly awaiting the ability to call C++ code from a UXP script.

For the time being, if you have .js code that needs to be protected from prying eyes, stash it in a .jsx and call it from .js through CSInterface, or else use the ‘standard’ approach of uglifying the code.


CRDT encryption/decryption is also tied into the tracking/licensing/activation mechanisms. It allows developers to add code to decide whether their plugin/script is active/not active/in demo/paid for mode…

Depending on the situation, their extension can stop working, or be limited in features – whatever they want.

Payment Handling

Currently I don’t have payment handling integrated yet; but I plan to build sample integrations for PayPal and Stripe.

Developers can then decide to either use my integrations or create their own.

For the time being, when using CRDT for licensing, users need to pay the developer via a standard invoicing mechanism, after which can be emailed an activation file generated in PluginInstaller by the developer.

This is a manual process, which should be manageable for small developers.

Eventually this will all be fully automated.

CRDT also has support for sub-licensing, and can handle floating licenses to support licensing a developer’s solutions to larger companies.

Creative Developer Tools Progress

Creative Developer Tools (CRDT) is taking shape!

The ExtendScript version (CRDT_ES) is now quite functional and usable.

I’ve used one of my old ‘free’ ExtendScripts (SizeLabels) as a real-life test, and wrapped it up as a CRDT package, using a lot of the code-signing, encryption, licensing, trialware, installer… features of CRDT to make it into a ‘real’ project that could start generating some revenue.

A more in-depth developer perspective on how I used CRDT to repackage SizeLabels in this YouTube video:


CEP Extensions: Packaging/Code Signing/Encrypting in CRDT

The next subproject I’ll be tackling in CRDT are CEP extensions.

All the CRDT for ExtendScript features are already available in CEP extensions – so the monetization and other features offered by CRDT can already be integrated and used.

But there is more work to do.

The next goal is to reduce the friction related to code signing, packaging and encrypting CEP extensions

If all goes according to plan, PluginInstaller will handle both the packaging (by the developer) and installing (by the end-user) of CEP extensions.

It will transparently handle both the ‘mock’ code signing (CEP code signing) enforced by Adobe as part of its security theater, and will add a layer of ‘real’ code signing on top of that (CRDT code signing).

And of course, CRDT will provide a developer with the features they need to monetize their work.

CEP Code Signing – ucf.jar

PluginInstaller needs to handle the CEP code signing, so I’ve been doing some research, and I’ve discovered that the good old ucf.jar in the crusty old Adobe signingtoolkit can be made to work and can jpackage-d with a recent Java JDK.

I’ve got it working on my M2 Mac, and will now integrate it into PluginInstaller.

I am hopeful this will allow me to enhance PluginInstaller to transparently manage CEP code signing

PluginInstaller will then automatically create the self-signed key and sign your extension, making the creation of a package just a little bit easier.

CEP Extensions: Packaging/Code Signing/Encrypting

The aim is for PluginInstaller to transparently handle two types of code-signing, and a layer of encryption.

  • on one hand handle the Adobe-enforced CEP code signing
  • additionally, and independently, it will handle CRDT code signing, which depends on a registry of public keys to keep track of developers and their signatures.
  • it will also handle CRDT code encryption which is an AES-256-based encryption meant to replace JSXBIN.

CEP Code Signing

The whole bother with Adobe enforcing developers to sign CEP extensions is nothing but security theater. By making PluginInstaller handle this, I can remove another hurdle for CEP extension developers.

These signing keys are not verified in any which way by the Creative Cloud apps, so anyone can create their own key and sign anything and the Creative Cloud software will accept it, sight unseen.

Any hacker can claim to be anyone else, and create a self-signed key to ‘prove’ it to the Creative Cloud apps, and the Creative Cloud apps will happily take them at their word.

Nothing stops a malicious hacker from unzipping someone’s genuine .zxp, adding some malware to it, and re-package it with their own self-signed key.

CRDT Code Signing

CRDT code signing requires developers register their identity in a registry, so the PluginInstaller can verify their signature.

When a developer creates a developer account in PluginInstaller, a public/private key pair is automatically generated and the public key is published in the registry.

Developers retain their own private key locally, and PluginInstaller will use the private key to code-sign their scripts and data files when it packages the software.

CRDT Code Encryption

Additionally, CRDT also implements CRDT code encryption. This allows developers to protect their code from prying eyes. This is a replacement for JSXBIN as provided by ExtendScript.

What’s ahead

There is still a lot of work to be done on CRDT. Important things that are on my to-do list:

  • integrate payment processors like PayPal and Stripe. At the moment, payments are still handled manually.
  • handle packaging and installing of UXP plugins the same way ExtendScript and CEP Extensions are handled, so PluginInstaller can be a one-stop shop both for packaging (developerland) and installing (userland)

Creative Developer Tools

The first public alpha versions of Creative Developer Tools for the Adobe eco-system are now becoming available.

UXP and UXPScript are great, but for software solutions that are running in a ‘clean room’, having to fight the UXP security sandboxes can make a developer’s life miserable.

Also, handling activation/licensing for commercial software can be a pain. Creative Developer Tools can handle that for you.

Creative Developer Tools for UXP currently works with Photoshop and InDesign and provides a growing set of APIs, including APIs for reading/writing binary files and text files without artificial limitations imposed by the UXP sandbox.



for more info.

The crdtuxp module contains a mix of synchronous and asynchronous functions.

Some functionality is written in JavaScript as part of crdtuxp.js, and is synchronous.

Other functions are delegated to a daemon process which is able to operate outside of the UXP sandbox, but still within the confines of the logged-in user. These functions are always asynchronous.

Important note: crdtuxp steps out of the UXP security sandbox – which means that, as a developer, you need to be judicious how and when to use this.

The reality is that every software system operates in a unique context.

UXP security measures have their place and can be helpful in keeping things secure when it comes to UXP development.

However, for many software systems, these measures are too strict and overbearing and don’t account for the actual operating environment. It’s an all-or-nothing approach.

In my opinion, it should be up to the user/developer/IT department to decide how to handle security.

Sometimes a particular workflow system will be living inside a walled garden, on a disconnected network, without any contact with the outside world and will not be allowed to run any unvetted software. Or othertimes the OS security is safe enough for the particular workflow at hand.

In those cases, the UXP security measures are counter-productive: they represent unnecessary hurdles to the software development, and often make the user interface clunky and user-unfriendly.

Using UXP sandboxing should be a developer-selectable option, not an enforced ‘always-on’ requirement, and it should be up to the developer and/or the IT department to decide what is appropriate and what not.

Creative Developer Tools puts the power back into the hands of the developer, the end-user and the I.T. department, and leaves it up to them to opt-in or opt-out of the UXP security sandbox.


JSXGetURL now live

JSXGetURL enhances ExtendScript to make it easy to access servers using httphttps, sftp… – e.g. to download assets from a remote server, straight from ExtendScript. It supports InDesign, Illustrator, Photoshop,… – any Adobe software with ExtendScript support.

There is nothing to install – all you need to do is add a single //@include line to your script.

I’ve further enhanced the License Manager, and JSXGetURL 1.0.4 is now live on the Rorohiko web site.

I’ve added a feature that allows ‘sublicensing’, where you can embed an activation code for JSXGetURL into your own solution, so your end-user does not have to deal with activations.

Have a look at



Tightener is our answer to a growing gap in automating Adobe Creative Cloud applications.

Remember the days when scripting Adobe apps didn’t require a CS degree? When people with a passion for printing, photography, or design could just jump in and start scripting? Those days seem far gone with the advent of UXP and UXPScript.

That’s where Tightener steps in. We’re gradually winding back the clock, making scripting approachable again, especially for those who have the domain knowledge and the know-how but don’t feel compelled to get a degree in computer science as well.

Tightener is a toolbox for developers, easing everything from script writing to monetization, from inter-app communication to usage tracking. It’s a diverse platform, and we’re just scratching the surface.

And for small developers and not-for-profit: Tightener is free for you! We’re all about empowering small developers, handling the nitty-gritty of activations and metrics, so you can focus on what you do best.

The goal is to make other small developers successful and let Tightener rise along with them.

This release marks the debut of the licensing/activation module in the Tightener toolkit.

It’s fresh off the press and in a ‘stable alpha’ state. Yes, there are a few rough edges, but it’s ready for action.

I have integrated Tightener’s activation/licensing logic into JSXGetURL. From 2024 onwards, JSXGetURL will be a commercial product:


Note 12-Feb-2024: the next in line is Creative Developer Tools:


Why am I using JSXGetURL for the debut of Tightener? JSXGetURL is the perfect test bed – a modest user base, all fellow developers.

In the coming months, I’ll be converting some more of the commercial Rorohiko tools and scripts to incorporate the Tightener activation/licensing system.

I invite you to take JSXGetURL for a spin. Without activation, it starts in demo mode, so you can explore without cost.

If you hit a snag or have suggestions, drop me a line.

Curious about Tightener? More (currently somewhat outdated) info on github:


In the coming months I’ll be updating and expanding the wiki, writing a lot of new and updated documentation here – for example, how to use the licensing/activation features in your own scripts.

JSXGetURL 2024 Update


I’ve just rolled out JSXGetURL 1.x.x; it’s now stepping into the commercial arena.

JSXGetURL enhances ExtendScript to make it easy to access servers using httphttps, sftp… – e.g. to download assets from a remote server, straight from ExtendScript.

There is nothing to install – all you need to do is add a single //@include line to your script.

You can get more info and download JSXGetURL from the Rorohiko web site:


JSXGetURL works with any Adobe Creative Cloud application that has ExtendScript support – InDesign, InDesign Server, InCopy, Illustrator, Photoshop…

You can obtain individual JSXGetURL seats for US$49/year.

To purchase and activate, you need to use the License Manager which is included in the Helpers subfolder.

Create an account by clicking the New button, lower right-hand corner, and choose an unlock code.

Register the account, then use the Store window to access the JSXGetURL catalog entry.

Save and then email a purchase order to [email protected].

Payments are not handled automatically yet – you need to manually send the amount by way of PayPal, to recipient [email protected].

If you get stuck, please reach out to [email protected] or [email protected]!

JSXGetURL 0.1.0

Note 12-Feb-2024: This post is outdated. Read about the latest JSXGetURL here:


JSXGetURL enhances ExtendScript to make it easy to access servers using http, https, sftp… – e.g. to download assets from a remote server.

JSXGetURL is meant to work with any Adobe Creative Cloud application that has ExtendScript support – InDesign, InDesign Server, InCopy, Illustrator, Photoshop…

JSXGetURL is implemented as an ExtendScript DLL/framework, written in C/C++.

This DLL allows you to access URLs (including https: or ftp:) straight from ExtendScript.

There is nothing to install – all you need to do is //@include a .jsx file which then loads the DLL.

Sample code:

//@include "JSXGetURL/JSXGetURLLoader.jsx"
var getURL = JSXGetURL();

getURL.addRequestHeader("Accept: */*");
var s = getURL.get("https://www.rorohiko.com") + "";


var headers = getURL.getResponseHeaders();

// Some random FTP file for testing

var f = new File("~/Desktop/sha512.sum");
var s = getURL.get("ftp://cygwin.com/pub/gcc/sha512.sum", f.fsName);

// Some zip file to test binary file download

var f = new File("~/Desktop/FrameReporter.1.1.8.zip");
getURL.get("https://www.rorohiko.com/downloads/FrameReporter.1.1.8.zip", f.fsName);

The previous 0.0.9 version was time-bombed for June 30, 2023 – which is 6 days from now, as of this writing.

Version 0.1.0 runs until Dec 31, 2023 and can be downloaded here:


I am still working on the licensing functionality in Tightener, which will ultimately be used to elevate JSXGetURL to a commercial product.

No firm decisions have been made yet, but come 2024, the licensing model for JSXGetURL will probably be a yearly recurring per-workstation fee, somewhere around US$30.

JSXGetURL versions for InDesign Server will be a fair bit more expensive.

Unlimited licenses will probably also be available, based on some yearly fee of a low multiple of US$1000.

All of this is still very much undecided – more later!

Preparing for ‘Pragmatic Extension Development’ workshop

To get the most out of my workshop ‘Pragmatic Extension Development’ in Phoenix, Arizona on June 7th, attendees can make some preparations.

(click link, scroll down to ‘Sessions’)

If any of the tasks listed below are unclear, or too daunting, don’t worry – I’ll take you through it at the workshop. Try to get as far into it as you can.

You need the Creative Cloud app installed, and the latest version of InDesign, and a few more Creative Cloud apps. Even though I’ll be using InDesign a lot, most of the material in the workshop applies to all ExtendScript-enabled or UXP-enabled Creative Cloud apps.

You need to have basic command-line git software installed, as well as a recent version of Node.js (https://nodejs.org/en).

On Mac, I recommend installing Homebrew (https://brew.sh/) or a similar package manager, and then using brew to install various necessary components like gh or node

You should also install the Github command-line tool gh (https://cli.github.com/).

A GUI client for git is also helpful. I use both
SourceTree (https://www.sourcetreeapp.com/)
SmartGit (https://www.syntevo.com/smartgit/).

You also need Visual Studio Code (https://code.visualstudio.com/) – not to be confused with Visual Studio. If you’re on an M1 Mac, you must install the Intel version or the Rosetta version, and run it in emulation mode.

Install the following extensions:

If you’re on Windows, you also want the old ExtendScript Toolkit (in the Creative Cloud App, bring up the settings, then Apps then enable Show Older Apps, then install ExtendScript Toolkit)

Clone the repositories:




somewhere on your computer, using your favorite git tool.

Please revisit this page regularly in the coming days – I might add some more ”things to do’ to it as I come to think of them.

Software registration and licensing control with Tightener

What this is


Software registration and licensing control with Tightener

As part of the Tightener automation glue (TightenerDocs), I want to design a system that would allow software developers and scripters to handle licensing and tracking of their software.

This system would provide the following features:

  • Provide unique GUIDs for users of the software
  • Provide unique GUIDs for ‘capabilities’ – think ‘licenses’, or ‘permissions’.
  • Uniquely identify individual computers (properly handle clones of computers – e.g. detect cloned virtual machines or image backup restores)
  • Retain no identifiable user data in the registry

The system is based on a central registry with a https-based API which would run on the domain tgrg.net.

This registry contains the absolute minimum of data to make the system work.

It contains no identifying information for any of the users or developers, nor does it contain any information about the capabilities being issued.

The idea is that if the registry were ever to be breached by hackers, there would be no useful data to be found in the registry that could be abused.

GUIDS and hashes

The system uses both randomly generated GUIDs and 128-bit hashes that are GUID-like.

I’ll use the term GUID in both cases – i.e. the hashes are calculated GUIDs and the rest are randomly generated GUIDs.

Software developer: generating a capability file

A software developer wants to grant a license to some other user.

Both the developer and the user are registered with the Tightener registry, and have an identifier – a calculated entityGUID.

An entity might register multiple times, and have multiple entityGUID.

The developer has some ‘out-of-band’ communication with their customer – they might be selling software via a website, or might be in email contact, or something else…

The developer handles operations like payments, issuing,… any way they want.

At some point in time, the developer and the user will have reached an agreement for the developer (the issuer) to grant the user (the grantee) some capability (e.g. a license to the software).

The developer will then create a data structure which encodes whatever data they need to pass to their software when it is running on the user’s computer.

E.g. they might have

  • settings to have certain features enabled/disabled
  • a maximum count of transactions allowed
  • a registered name that needs to be watermarked into the output
  • some segment piece of code that the software needs to run
  • encrypted data, which only the developer’s own software can decrypt

Tightener does not care whatever this data is.

The developer’s data structure is added to a capability wrapper file.

The developer then registers a calculated GUID-like hash of the file with the Tightener registry (called a capabilityGUID).

Finally, they also send a copy of the file to the user.

Registering the capability wrapper file with the registry will only store a hash of that file into the Tightener registry.

The file itself is not stored in the registry, and the registry has no knowledge of the contents of the file. All it knows is the existence of the file, not the contents, nor who created it or who the grantee is.

Capability Slots

In the end, the user ends up with a copy of the capability wrapper file.

The Tightener registry knows about the existence of the file but does not know what its contents are.

Each one of these registered capability wrapper files represents a unique ‘capability slot’ in the registry.

Any such slot can only be assigned to a single computer.

The end-user cannot re-use the capability on multiple computers.

If the software developer allows this, they user can ‘shift’ the capability from one computer to another, but they cannot use it on two computers at the same time.

User registrations

Any entity (user, company, developer,…) needs to be registered with the Tightener registry.

This registration entails no more than a calculated GUID and a public encryption key.

The entities themselves need to hold on to the corresponding private keys.

In other words, the stored data in the registry is very ‘thin’.

No identifiable data is stored in the registry – just a GUID, a public key and a time stamp.

There is more data linked to the entityGUID (e.g. a name, password, address,…) but none of that data is ever stored in the registry.

The additional user data associated with the entityGUID is only known to the entity themselves (i.e. they have the data in store and can prove to any third party that this data matches the entityGUID).

In the registry, the only footprint of an entity is a GUID, a corresponding public encryption key and a time stamp.

Machine registrations

Any computer in the ecosystem needs to be registered in the Tightener registry.

Again, this data is very thin. Each machine is identified in the registry by a long-term-persistent generated registryMachineGUID.

This generated registryMachineGUID is used to tie capability slots to individual machines.

A capability slot can be either empty, or contain a hash which combines registryMachineGUID, capabilityGUID and granteeGUID.

The registry is not meant to have any knowledge of the granteeGUID.

To accomplish that, the protocol uses a value capabilityGranteeHash = hash(capabilityGUID + granteeGUID) which is provided to the registry by the remote machine during the capability verification process.

Assigning a machine to a capability slot is done by putting

hash(registryMachineGUID + capabilityGranteeHash)

in a field of a record of the capabilitySlot database table on the registry.

This allows the registry to verify whether a particular registryMachineGUID is assigned to a particular capability slot.

Once registered, machines have three generated GUIDs, each useful within a different context.

There is a long-term stable registryMachineGUID which is internal to the registry. Only the registry uses and has access to these.

There is a long-term stable localMachineGUID which is internal to the machine itself. It is not stored in the registry, only the local machine has access to these.

Then there is a short-term, unstable machineLinkGUID which is stored on the machine itself as well as in the registry. It is used to communicate about the machine between registry and the machine.

At any one time, one machine will only have one unique registryMachineGUID, one unique localMachineGUID and one unique machineLinkGUID.

The machineLinkGUID will occasionally be replaced to handle various external circumstances.

The machineLinkGUID helps detect cloning of machines: because they’re clones, cloned machines will start out with a duplicate of the parent machine’s machineLinkGUID.

This is detected by the registry, after which the cloned machine will automatically be assigned a new, different registryMachineGUID, a new localMachineGUID and a new machineLinkGUID.

Verifying a capability

When some software launches on the user’s computer, it can interact by way of the local Tightener bus with the locally running Tightener Registry Node, which handles the protocols and interaction with the Tightener registry.

The Tightener Registry Node needs access to the local copy of the capability wrapper file and the computer’s local data store.

The Tightener Registry Node interacts with the tgrg.net Tightener registry, and depending on the result, it will either decrypt the capability data (if the capability is granted) and pass it to the software, or it will return an error (if the capability is not granted).

The software can then do whatever it wants to do based on that result (e.g. switch to demo mode, bail out, add the watermark to the output, limit the number of produced files,…)

Tables in the registry

All the data in the Tightener registry consists of three tables:

Entities (users, companies, developers…):


Capability slots:

hash(registryMachineGUID + capabilityGranteeHash)

Machine registrations:


Denial-of-Service prevention:


There is no readable data in the registry – any and all readable data is retained on individual’s computers.

All registryTimestamp in the database tables are based on the clock of the registry. They record the time of the last update to the table.

The Denial-of-Service prevention table contains temporary data (i.e. records get wiped after a period of non-activity for an entityGUID, e.g. after a day). It helps detect rogue entityGUIDs that are re-occurring abnormally fast and furious.

Hardware GUID

Tightener can calculate a hardwareGUID for the computer it is running on.

The only purpose of the hardwareGUID is to detect a possible configuration change in the computer or its surroundings.

The hardwareGUID is not used to identify computers and the hardwareGUID does not need to be stable or globally unique. Collisions are not a concern.

The hardwareGUID is never sent to the registry.

This calculated GUID is a hash which combines a bunch some contextual factors. Things like

  • Ethernet MAC address
  • RAM size
  • computer name
  • operating system and version

These external factors will be chosen to be somewhat stable over time.

Changes should preferably only occur occasionally, over time spans of months, weeks or days. A calculated hardwareGUID will hopefully remain the same for some period of time.

Registry API (POST, not GET)

The registry API is used by the locally running Tightener Registry Node which handles the https-based protocol interaction with the central Tightener registry to support all Tightener-enabled nodes (softwares) on the computer.

Any third-party software using the Tightener system will use the Tightener messaging bus to interact with the locally running Tightener Registry Node.

Below some information about how the Tightener Registry Node goes about its business.

The Tightener registry is an entity

The Tightener registry itself is also considered an entity and also has a corresponding entityGUID and a public key in the registry.

This key is needed by any Tightener Registry Node interacting with the Tightener registry to be able to encrypt and decrypt data being sent to/from the registry.


All communication with the registry uses POST interactions over https.

The certificate of the registry is on the tgrg.net domain.

This helps the remote computers ascertain they are really talking to the actual registry and not an imposter.

All data that is sent from the Tightener registry into a remote machine is encrypted with the Tightener registry’s private key. The receiving machine decrypts the data using the public key for the Tightener registry.

All data sent from a machine to the Tightener registry is encrypted using the Tightener registry public key.

Only the registry software can then decrypt this data using its private key.

Scenario: fetch the registry’s public key


This returns

    entityGUID: <GUID>,
    key: <publickey>

where entityGUID is the entityGUID assigned to the registry itself.

Scenario: Check computer registration


At certain times (e.g. startup, every day,…) Tightener on the remote machine will read local preferences from an encrypted local prefs file.

It retrieves the following data from the local store (if any):


If localMachineGUID is empty, a random GUID is generated. This GUID will be persisted in local storage.

Tightener then recalculates a hardwareGUID in order to detect changes in the environment.

If a change is detected, or if a certain amount of time has passed since the last verification it will reach out to the registry and the registration is verified with the registry.

If the hardwareGUID detected an environmental change, the API call is updatemachine

    machineLinkGUID: <GUID>,
    registryTimestamp: <timestamp>

If no environmental change was detected, then the API call is verifymachine.

    machineLinkGUID: <GUID>,
    registryTimestamp: <timestamp>

If the prefs are empty (no machineLinkGUID nor registryTimestamp), the API call is newmachine


For updatemachine and verifymachine, the registry will now look for a record that matches both machineLinkGUID and registryTimestamp.

Three scenarios can occur:

  • For newmachine or if no matching record can be found: create a new record with a random new machineLinkGUID and a random new registryMachineGUID and the current registryTimestamp.
  • For the updatemachine call: update the matching record with a new random machineLinkGUID and the current registryTimestamp.
  • For the verifymachine call: update the matching record with the current registryTimestamp.

This allows the registry to detect cloned machines: if two machines share the same machineLinkGUID, one of them will go through the verification process first, and by doing so, will modify the registryTimestamp associated with that machineLinkGUID in the registry database.

The second machine might try to use the same machineLinkGUID, but it will not be aware that the time stamp in the registry has been updated after the first machine had its interaction, so the second machine will send the wrong time stamp, and will be forcibly issued a new random machineLinkGUID and a new random registryMachineGUID by the registry.

Doing this will also sever the link to the cloned machine from any capability slots.

This API call returns a machineLinkGUID and the registryTimestamp from the updated database record.

    machineLinkGUID: <GUID>,
    registryTimestamp: <timestamp>,
    isNewMachine: <boolean>

The returned machineLinkGUID will often, but not always, be different from the machineLinkGUID that was sent to the registry at the start of the verification process.

The isNewMachine field is set to true when the registry created a new registryMachineGUID and registered a new record (i.e. when a new machine or a clone was being registered)

If isNewMachine is true then the local machine will generate a new, random localMachineGUID.

The received data (machineLinkGUID and registryTimestamp), together with the recalculated hardwareGUID and the localMachineGUID are then saved into the machine’s local store, to be used again in a future verification round.

To recap:

The machineLinkGUID is a GUID that uniquely identifies the computer. This GUID is not stable over time.

The machine will be issued a new machineLinkGUID each time the computer hardwareGUID changes, or when the registry detects that the machine is a clone of another machine.

The registry tracks this machineLinkGUID and links it to a stable, long term registryMachineGUID which in turn is used to link a machine into zero or more capability slots.

The local machine has no need to know its own registryMachineGUID.

Instead, it locally persists its own localMachineGUID. This localMachineGUID is never communicated to the registry.

Scenario: Register a capability


An ‘issuer’ entity (e.g. a software supplier) wants to issue a capability to another entity (e.g. a ‘user’).

In order to do so, this issuer will formulate a capability data structure.

This capability data structure is a string with a JSON-encoded data structure. It can be as simple as just a string or it can be an intricate, complex data structure.

It reflects the capability that will be granted to the user – e.g. a software license, a time-limited software license, a throughput value, watermarking info, some critical segment of code that the software needs to run, some privately encrypted data, decryption keys, any other data…

Tightener does not care about the data or the structure inside the capability data structure.

The capability is encoded as a JSON string, and then encrypted using the issuer’s private key.

This data is then embedded into a capability wrapper file, and a random generated GUID is added as salt.

capabilityWrapper = 
    issuer: <GUID>, 
    grantee: <GUID>, 
    capability: <encryptedCapability>, 
    salt: <GUID> 


capabilityGUID = hash(capabilityWrapper) 

The issuer will then registers the capabilityGUID with the registry.

This registration establishes a new capability slot in the registry.

    issuer: <GUID>,
    grantee: <GUID>, 
    capabilityGUID: <GUID> 

which returns

    success: <boolean> 

This adds a record to the table with capability slots.

The issuer and grantee GUIDs are not stored in the main registry tables, but a temporary record is made of these GUIDs to try and detect denial of service attacks: if the same issuer or grantee gets registered many times in rapid succession, that issuer or grantee will be blocked from further capability registrations. The Denial of Service detection data is fleeting and gets wiped after some period of non-activity.

The issuer then passes a copy of the capability wrapper file to the grantee (e.g. via email, download from web server,…)

Scenario: Register User or Entity


The entity (user, company, developer…) chooses or creates a identifying ‘handle’ .

This handle can be anything: a simple string or a complex JSON structure encoded into a single string.

They also pick a password. Then we calculate:

entityGUID = hash(hash(entityHandle) + hash(password)) 

The entity also creates a public/private key pair at the same time.

Finally, the entity needs to provide a currently valid email address.

This email address only needs to be valid during the registration process; it is not used afterwards.

Register with registry:

    entityGUID: <GUID>,
    publicKey: <Key>,
    entityEmail: <email>


     success: <boolean> 

The registry also sends out an email with a registration confirmation link.

In the database table, entityEmailHash will stores a GUID-like hash of the entity’s email.

The registry will not retain the email address long term. Instead it stores a hash:

entityEmailHash = hash(entityEmail.toLowerCase()) 

However, during the pending registration process, for a short time, the entityEmailHash field will store the actual email address.

This is until the registry receives confirmation of the registration when the user clicks on a confirmation link in an email, or when a certain amount of time lapses.

If the confirmation link is not clicked within the allotted time, the record is deleted and the registration is nullified.

From the moment the registration link is clicked, the entityEmailHash field is replaced by the hash.

From then on we can still verify the validity of an email address provided by the user, but we cannot retrieve the email address from the server data.

This also offers some protection against denial of service attacks: registering the same email address twice is not possible.

Scenario: Check/Verify User

    entityGUID: <GUID> 


     publicKey: <Key> 

Scenario: Request capability


User has a local copy of a capability wrapper file.

The computer has been registered and has a machineLinkGUID.

Some third party software in the computer needs to verify that the capability has been granted and needs access to the capability data.

The Tightener Registry Node will retrieve the local user’s entityGUID from the local storage.

There might be more than one entityGUID if the user has registered more than once with different handles – that’s allowed.

The Tightener Registry Node will verify that one of the stored entityGUID matches the grantee in the capability wrapper file.

If no matching entityGUID can be found, the user needs to ‘log in’ to the system by providing the correct handle and password which are used to re-calculate their entityGUID which should be a match to the grantee GUID.

If the user cannot provide the necessary data (e.g. don’t know the password) to make the entityGUID match the grantee GUID, then they are not ‘logged in’ and cannot use the capability.

No interaction with the registry is needed to handle a ‘log in’.

If successful, the Tightener Registry Node will then store this entityGUID in local storage as an additional valid local entityGUID entry for later use.

The Tightener Registry Node will then calculate the capabilityGUID by hashing the capability wrapper file.

Then it will make a requestcapability registry API call.

capabilityGranteeHash is calculated as hash(capabilityGUID + granteeGUID).

The registry will not be able to determine the granteeGUID from that data. It needs this capabilityGranteeHash to verify or calculate the hash used to record the occupancy of the capability slot.

    capabilityGUID: <GUID>,
    capabilityGranteeHash: <GUID>,
    machineLinkGUID: <GUID>

which returns

    status: <number 0-4>,
    registryStatusTimestamp: <timestamp>,
    registryTimestamp: <timestamp>

registryTimestamp is the current time at the registry.

Status 0 means: the capability slot is unknown. registryStatusTimestamp is empty.

Status 1 means: the capability slot is valid, but is assigned to another computer with a different machineRegistryGUID.
registryStatusTimestamp tells the computer when the capability slot was last assigned.

Status 2 means: the capability slot is valid and is already registered to the computer making the request. All good.

Status 3 means: the capability slot is valid and is not yet assigned to any computer (i.e. it’s a brand new capability).

In case of status 1 or 3, the Tightener Registry Node will inform the software that the user might need to be queried: “This computer is not assigned to a capability slot, but there is a capability slot we can assign or override. Do you want to ‘grab’ the capability for this computer?”

It is up to the software to handle this and determine whether the user should be allowed to reassign the capability slot – e.g. the software could determine if there is enough difference between the registryTimestamp and registryStatusTimestamp

If affirmative, a further request can be made which should result in a status 2 when successful.

    capabilityGUID: <GUID>,
    capabilityGranteeHash: <GUID>,
    machineLinkGUID: <GUID>

which returns

    status: <number 0-4>,
    registryStatusTimestamp: <timestamp>,
    registryTimestamp: <timestamp>

with the same status values as before.

After receiving a status 2, the Tightener Registry Node will then fetch the issuer’s public key from registry (using https://tgrg.net/checkuser)

It decrypts the capability from the capability wrapper using this key.

Finally, it passes the decrypted capability to the software that initiated the request.

    entityGUID: <GUID>,
    capabilityGUID: <GUID>,
    localMachineGUID: <GUID>,
    capability: {... decrypted data for use by software ...} 

The software now has access to a

  • unique user-assigned GUID tied to the user (entityGUID)
  • unique computer GUID (localMachineGUID)
  • capability data

The software also has access to the capabilityGUID which is a unique ID for the capability and the localMachineGUID which is a unique, stable ID for the machine.

It is up to the software developer how to use this data – e.g. the entityGUID or localMachineGUID might be used as a key in the software developer’s databases, but the Tightener registry contains none of that knowledge. Whether to retain or not retain any user info is up to the software developer. Tightener does not limit nor enforce anything in this regard.

Stuff to consider

What happens when the registry goes off-line? Need to think about some duplication or fallback. Might have some contract to allow software developers to run separate mirrors of the registry so the registry is never off-line.

Do we need a system to revoke capability slots? On registering the issuer could also provide a revokeGUID which is stored in the database. Something like:

Issuer picks a revokePassword and calculates

revokeProofGUID = hash(revokePassword + capabilityGUID) 

then calculates

revokeGUID = hash(revokeProofGUID + capabilityGUID) 

and sends this to registry along with the capability registration.

The registry would then also store the revokeGUID in the capabilitySlot record.

When issuer wants to revoke, they type in their revokePassword and recalculate the revokeProofGUID locally. Then they provide the registry with the revokeProofGUID and the capabilityGUID.

The registry then verifies that the recalculated revokeGUID is a match, and then it knows the capability slot can be deleted.

What happens when something goes wrong. For example, the connection might break during a machine verification, causing the machineLinkGUID to fail to update. Maybe we need an additional confirmation transaction at the end.

GUID contexts

There are four contexts in the protocol: local (on the user’s machine), registry (on the Tightener registry), and issuer (at the software developer’s), and software (inside the software while it runs on the user’s machine)

Most GUIDs are only known and available in a few context.

hardwareGUID: local
localMachineGUID: local, software
capabilityGUID: local, registry, issuer, software
revokeProofGUID: issuer, registry (not persisted)
revokeGUID: issuer, registry
entityGUID: local, registry, issuer (granteeGUID), software
machineLinkGUID: local, registry
capabilityGranteeHash: local, registry (not persisted)
publicKey: public
entityEmail: local, registry. Becomes a hash after completion of registration.
registryMachineGUID: registry