The PixInsight Script Code Signing System

By Juan Conejero (Pleiades Astrophoto S.L.)


A description of the new script code signing infrastructure available since PixInsight 1.8.9. [more]

Keywords: script code signing, code signing, code signature, code security, certified developer, CPD, cryptography, security

Contents

[hide]

1 Introduction

[hide]

Version 1.8.9 of PixInsight introduces a new secure script code signing infrastructure. In this document we provide a general description of this important new feature, along with technical information for developers. Besides technical aspects, all of our users should be aware of the importance of code security, of the risks posed by a scriptable development platform like PixInsight, and know how we are working to make PixInsight as secure as possible.

Scripting resources are extremely powerful and versatile in PixInsight, and the degree of sophistication of the scripts available on our platform is now really amazing. All of this sophistication, along with the open nature of scripting code, involves important risks to both our users and our platform. A script can be altered very easily to perform all kinds of malicious or even destructive actions, including exploits that can compromise data and identities. With the improvements that we are going to implement in the PixInsight JavaScript Runtime in future versions (such as a new JavaScript engine with much more capabilities, a hybrid JavaScript/C++ runtime, etc), these risks will be even more relevant.

To understand the risks involved, let's put an example. As you know, our automatic update system allows a developer to distribute his or her code to the entire community of PixInsight users. When an update is received, accepted by the user and installed, our updater program is executed with elevated privileges (i.e., root access) in order to be able to change files on system protected directories. This means that an update has potentially full access to every user's PixInsight installation.

Now Imagine that 'some developer' manages to convince some of our users to add his update repository to their PixInsight installations, so that they can use his nice and useful scripts. Then with one of the updates, along with his scripts, the 'developer' alters PixInsight's standard startup script (the well-known startup.scp) to gather personal information and send it somewhere, or even worse, to execute a program that infects the machine. Since the startup script runs automatically each time PixInsight is executed, this is an efficient and discrete way to distribute malware and steal personal data using PixInsight as a powerful Trojan horse. This can happen, unless we guarantee that scripts come from trusted sources and cannot be altered, by means of special developer identities and signatures. And this can only be done with a code authentication system, as the one that we have now since version 1.8.9 of PixInsight.

Obviously this situation requires an action on our part to make PixInsight a more secure development platform. Since version 1.8.9, we start a program of Certified PixInsight Developers (CPDs) and a new Secure Script Code Signing System. CPDs are recognized software developers with the right to generate signatures for their scripts and update repositories. Code and repository signatures enforce security of executed scripts, provide reputation to developers, and guarantee that the code being executed comes from a trusted source and is exactly the same that the original developer has released. Signature entitlements allow execution of special actions that are considered potential security risks, both to the user and to PixInsight.

We want to introduce code security as an important added value and a clear sign of evolution of our platform, but without causing problems to existing scripts and dependent implementations. Our intention is not to limit the user's freedom, but to take our responsibility by improving PixInsight as a secure environment. In version 1.8.9-1 of PixInsight we have implemented our complete code security system, which is now fully functional and includes all of the required features and resources to make code signing an easy and non-intrusive task for all developers. We expect that most developers on the PixInsight platform will be interested in becoming CPDs in the immediate future.

2 Signatures

[hide]

The PixInsight code security infrastructure implements a public-key cryptographic system.[1] This means that each signing identity (or 'developer') must have two different keys: a private key and a public key. Generation of code signatures requires both keys, while signature verification requires exclusively the public key. Our code signatures use the Ed25519 public-key signature system[2] [3] and SHA-512 cryptographic checksums.[4] As we'll describe later in this document, signatures for scripts are stored as separate files (with the .xsgn extension), while signatures for update repositories are included in the corresponding repository information documents (.xri extension).

Our implementation of the Ed25519 signature system is based on the portable C implementation by Orson Peters,[5] which in turn is based on the SUPERCOP 'ref10' implementation.[6] For generation of signing key pairs, our implementation relies on platform-dependent, cryptographically secure random seeds. On Linux and macOS we use the standard /dev/urandom random number generator.[7] On Windows we use the BCryptGenRandom Win32 API function.[8]

3 Certified PixInsight Developers

[hide]

A Certified PixInsight Developer (CPD) has the right to generate code signatures that will be recognized as valid by the PixInsight core application. It is important to understand that code signatures don't guarantee anything about the quality of signed scripts. It is not quality, suitability, usefulness or the developer's talent what we are guaranteeing here, but only authenticity and integrity. Besides this, we try to make sure, to the extent that can be reasonably possible, that a CPD is someone who is not going to damage your machine or steal your data. In other words, we try to ensure that CPDs are honest and serious people contributing their work to make PixInsight and the life of PixInsight users better.

The PixInsight distribution includes a database file in XML format, namely:

<install-dir>/PixInsight/etc/security/certified-developers.xcdev

with the list of currently recognized CPDs. This database is updated regularly as new CPDs are known to the platform. Signatures generated by these CPDs (and also by the local signing identity, which we describe in the next section) will be trusted by the PixInsight core application when a signed script is executed, or when a signed update repository is checked for available updates.

For each CPD, the .xcdev database file stores the following data:

Identifier

Unique developer identifier. This identifier will be used to associate code signatures with certified developers during the signature verification process. For example, our corporate CPD identifier is 'PTeam'.

E-mail

Optional e-mail address of the CPD.

URL

Optional URL of a website maintained by the CPD.

Name

The CPD's real name.

Additional information

Optional brief information about the CPD, its work, etc.

Public key

The encrypted public signing key. This key will be used to validate all code signatures associated with the CPD's identifier.

For encryption of public signing keys we use a custom algorithm based on AES-256.[9]

To become a CPD you should use the standard SubmitCPD script to send us your identifying data and public signing key.

4 Local Signing Identity

[hide]

The local signing identity allows you to generate script code signatures using your software license to identify you as a local certified developer, without needing to become a Certified PixInsight Developer (CPD). This is useful for testing purposes, or when you are writing scripts that require special security entitlements for your private use, or whenever you want to be sure that the code you have written remains unaltered.

Since the local signing identity is linked to a particular PixInsight software license, it can only be used on machines where the corresponding license has been activated, or more precisely, where a valid license file for that license exists. The local signing identity can be made persistent on a PixInsight installation, so it can be remembered and used across successive core application executions, making its creation basically a one-time task. The Script > Local Signing Identity... main menu command can be used to manage local signing identities.

5 Secure Signing Keys Files

[hide]

A secure signing keys file (with the .xssk file extension) stores a pair of signing keys strongly encrypted with a user-defined password. In this way you can have your private and public signing keys readily available on a local machine without the risk of them being stolen or used to impersonate your identity. Naturally, for this to be true you must use strong passwords to protect your .xssk files.

The following example shows the (abbreviated) contents of a signing keys file generated for a fictional 'NyotaUhura' CPD identity.

<?xml version="1.0" encoding="UTF-8"?>
<!--
PixInsight XML Secure Signing Keys Format - XSSK version 1.0
Created with PixInsight software - https://pixinsight.com/
-->
<xssk version="1.0" xmlns="http://www.pixinsight.com/xssk"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.pixinsight.com/xssk http://pixinsight.com/xssk/xssk-1.0.xsd">
   <CreationTime>2422-04-04T11:19:11.920Z</CreationTime>
   <KeyPair version="1.0" signatureSystem="Ed25519" developerId="NyotaUhura">
      <PublicKey encoding="Base64">ii2bAL+xQfXT1XJuJ434ABfuTRj3NZpjSyqicTW2cXVCc...</PublicKey>
      <PrivateKey encoding="Base64">sT4CoUGTGS9Yamu6ujuStJImJsQkri3cVbgtTk2poX/r...</PrivateKey>
   </KeyPair>
</xssk>
<Checksum encoding="Base64">LXVjOecX3VWnGFOfAIvEwiz1bnZvBAbTQaeGsuX3WVZpdR8l9gFb...</Checksum>

For encryption of signing keys in .xssk files we use a custom algorithm based on AES-256[9] As you can see in the example, a signing keys file includes an encrypted checksum, so .xssk files are effectively sealed and protected against alterations. The Checksum element is generated from the SHA-256 digest of the canonicalized main XML element (the xssk top-level element), encrypted with the same algorithm based on AES-256.

For secure signing keys file generation you can use the standard SigningKeys script.

6 Script Signature Files

[hide]

PixInsight script signatures are stored in special XML files with the .xsgn extension, which we know as signature files. When a source code file is signed, the following procedure is applied to generate a signature file:

  1. The contents of the source code file are loaded.
  2. The script identifier is acquired from an existing #feature-id or #script-id directive. Presence of one of these directives in the main script file is mandatory for signature generation.
  3. For each #include directive, the corresponding file is loaded and its contents are inserted to replace the directive. This task is repeated recursively to load all included files, in the exact order and locations of their originating #include directives. Note that this comprises the entire source code that will or could be executed, since conditional directives that may condition file inclusion during script execution are ignored.
  4. The loaded source code is canonicalized by removing all code comments, all empty lines, and any leading and trailing white space characters from each source code line, since these are irrelevant for execution of code written in the JavaScript and shell scripting languages.
  5. The developer identifier is appended to the canonicalized source code.
  6. A timestamp string with the current UTC date and time in ISO 8601 format[10] is appended to the canonicalized source code after the developer identifier.
  7. An optional list of entitlements, if not empty, is appended to the canonicalized source code after the developer identifier and the timestamp.
  8. A cryptographic digest or checksum is computed from the entire body of source code with developer identifier, timestamp and entitlements. Currently SHA-512[4] digests are used exclusively for code signing.
  9. A signature is generated for the SHA-512 checksum using the private and public keys of a certified developer with the Ed25519 algorithm.[2] [3]
  10. The Ed25519 signature, along with the script identifier, the developer identifier, the timestamp and the optional list of entitlements, are serialized as an XML document.
  11. The XML document is encoded as UTF-8[11] and stored as a file with the .xsgn extension.

In step 3, platform include files stored on the standard include/pjsr directory (and recursively, on any of its child subdirectories) are considered secure and hence not loaded as part of the canonicalized code to be signed. This allows us to update standard include files without invalidating existing signatures for scripts that depend on them. However, each file on the include/pjsr directory (or subdirectories) must have a valid signature file, which will be verified during the code signing and signature validation processes.

A script signature file has a few additional requirements, which are enforced during signature generation and verification:

  • It must have the same filename as the signed file with the .xsgn extension.
  • It must be stored on the same directory as the corresponding signed file. In other words, you can move a signed script, but the script and its signature must always be moved together and remain at the same location in a local filesystem.
  • On systems that support them, symbolic links cannot be used to move or replace signature files.

The example below shows the contents of a signature file generated with our PTeam corporate CPD identity for the standard WeightedBatchPreprocessing script.

<?xml version="1.0" encoding="UTF-8"?>
<!--
PixInsight XML Code Signature Format - XSGN version 1.0
Created with PixInsight software - https://pixinsight.com/
-->
<xsgn version="1.0" xmlns="http://www.pixinsight.com/xsgn"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.pixinsight.com/xsgn http://pixinsight.com/xsgn/xsgn-1.0.xsd">
   <CreationTime>2022-04-04T09:54:53.225Z</CreationTime>
   <Signature version="1.0" scriptId="WeightedBatchPreprocessing" developerId="PTeam">
      <Timestamp>2022-04-04T09:54:53.223Z</Timestamp>
      <Entitlements>com.pixinsight.security.pcl.experimental-features</Entitlements>
      <CodeSignature encoding="Base64">z0rmS9RS3Se1+Uz3RVX2fX7yNUodweMCcCPLGyWBNz4nk
         0MHDhEP9IxZvjFWnHMVsogB01g6v2EZUUxVCf+GAg==</CodeSignature>
   </Signature>
</xsgn>

To generate script signature files you can use the standard CodeSign script.

7 Script Code Signing Requirements

[hide]

As we have said in the previous section, a script must have a valid identifier in order to be signed. Script identifiers can be specified with the #feature-id and #script-id directives.

The #feature-id directive is best suited for JavaScript scripts that can be included in the Script main menu item, as well as on the Process Explorer window, either with featured scripts collections or by means of the Script > Feature Scripts... command. This directive has the following syntax:

#feature-id <script-id> : <menu-item>

where <script-id> is the script identifier and <menu-item> defines the menu item that will be created under the Script main menu. For example, here is how this directive is being used in the standard WeightedBatchPreprocessing script:

#feature-id WeightedBatchPreprocessing : Batch Processing > WeightedBatchPreprocessing

The #script-id directive is intended for scripts that cannot or shouldn't be included in the Script menu:

#script-id <script-id>

with <script-id> having the same meaning as before. For example, the standard startup.scp script begins with:

#script-id core_startup

8 Security Entitlements

[hide]

In the context of our code signing system, an entitlement is a string that grants a script permission to perform a specific task or action that may have an impact on the integrity of the system, on the user's privacy, or on the PixInsight platform.

Entitlements are encoded following the reverse domain name notation.[12] The following list describes the entitlements available in the initial implementation of our code security system, released with version 1.8.9 of PixInsight.

com.pixinsight.security.pjsr.allow-launch-application-instance

Entitlement required to call the CoreApplication.launchInstance() method.

com.pixinsight.security.pjsr.allow-activate-application-instance

Entitlement required to call the CoreApplication.activateInstance() method.

com.pixinsight.security.pjsr.allow-terminate-application-instance

Entitlement required to call the CoreApplication.terminateInstance() method.

com.pixinsight.security.pjsr.allow-load-resource

Entitlement required to call the loadResource() method of the Global object.

com.pixinsight.security.pjsr.allow-unload-resource

Entitlement required to call the unloadResource() method of the Global object.

com.pixinsight.security.pjsr.allow-write-global-settings

Entitlement required to call the Settings.writeGlobal() method.

com.pixinsight.security.pjsr.allow-remove-global-settings

Entitlement required to call the Settings.removeGlobal() method.

com.pixinsight.security.pcl.experimental-features

Entitlement required to execute experimental tasks and processes in PCL-based modules.

As you see, this initial implementation has a very reduced set of basic security entitlements. You can expect more entitlement requirements in future versions of PixInsight, including non-intrusive protection of sensitive files and folders, as well as protection against potentially dangerous actions for the integrity of the system and the PixInsight installation.

Under normal conditions all CPDs, as well as the local signing identity, can specify any entitlements for generation of script signatures without restrictions. You can specify entitlements when you sign your scripts with the standard CodeSign script, or directly from JavaScript code using the Security PJSR object.

9 Update Repository Signatures

[hide]

Since version 1.8.9-1 of PixInsight, all update repositories should be signed with a CPD identity. A PixInsight update repository provides the URL of a repository information document (with the updates.xri filename), which is a special XML format to describe update packages and their associated metadata.

Unlike script code signatures, repository signatures are included directly in .xri documents. The following example shows a relevant fragment from one of our standard update respositories.

<?xml version="1.0" encoding="UTF-8"?>
<xri version="1.0">
   <description>
      <p>
         PixInsight Updates — The main PixInsight update repository at Pleiades Astrophoto.
      </p>
   </description>

   <metadata id="20220329-module" releaseDate="20220329">
      <title>
         ImageCalibration module v.1.9.2 / ImageIntegration module v.1.4.7 (update/bugfix)
      </title>
      <description>
         ...
      </description>
   </metadata>

   <platform os="linux" arch="x64" version="1.8.9:1.8.9">
      <package fileName="20220329-linux-x64-module.tar.gz"
               sha1="89b109ff40e12d2597ac234c6c0d3fd9087d404f"
               type="module"
               metadata="20220329-module"/>
   </platform>

   <platform os="macosx" arch="x64" version="1.8.9:1.8.9">
      <package fileName="20220329-macosx-x64-module.tar.gz"
               sha1="191784d6099466821751bb1e696245eddfc011b1"
               type="module"
               metadata="20220329-module"/>
   </platform>

   <platform os="windows" arch="x64" version="1.8.9:1.8.9">
      <package fileName="20220329-windows-x64-module.zip"
               sha1="54e983fb4040b347c3507c859f8da625309227c0"
               type="module"
               metadata="20220329-module"/>
   </platform>
</xri>
<Signature developerId="PTeam" timestamp="2022-03-29T19:38:44.766Z" encoding="Base64">
   IFM42jkRaWQ459oeoPAdBgafvMFORjAaSeyZGeVO+25cyFrOoZiAxI3dgi6fyaYUSwl1qg/k1YeG013m6ZWMCg==
</Signature>

The Signature element provides a Base-64 encoded signature generated with the private and public keys of a CPD (our corporate PTeam CPD in this example) for the canonicalized root xri element. Repository signatures guarantee that update packages come from trusted sources and that repository information documents have not been altered or replaced with fake files. This is a crucial component of our code security system.

To generate update repository signatures you can use the standard CodeSign script.

Important— Repository signatures are not mandatory, but highly recommended. When one or more update packages are about to be downloaded from an unsigned repository, the PixInsight core application asks the user for explicit confirmation to perform the operation, informing about the potential risks involved. Future versions may implement more strict conditions and limitations for the deployment of unsigned updates.

10 The Security PJSR Object

[hide]

The Security object provides code security management and utility functions. In this section we describe the methods available for this object as of version 1.8.9-1 of PixInsight.


Array Security.certifiedDevelopers()

Returns an Array object describing the set of certified developers (CPDs) recognized by the running PixInsight core application. Each array element is an object with the following properties describing the identity of a CPD:

Property

Type

Description

id String

The developer identifier.

email String

The developer's public email address. Optional; can be an empty string or null.

url String

The developer's website URL. Optional; can be an empty string or null.

name String

The developer's actual name. Optional; can be an empty string or null.

info String

Additional information on the developer. Optional; can be an empty string or null.

publicKey ByteArray

The developer's public signing key (length = 32 bytes).


void Security.exportSigningKeysFile( String outputFilePath, String|ByteArray newPassword,
                                     String filePath, String|ByteArray password ) )

Generates a copy of an existing secure signing keys file without altering the original public and private signing keys.

Parameter

Type

Description

outputFilePath String

Full path to the output secure signing keys file. Must have the .xssk file extension.

newPassword

String or ByteArray

The protection password for the output signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.

filePath String

Full path to the input signing keys file to be exported. Must have the .xssk file extension.

password

String or ByteArray

The protection password for the input signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.


void Security.generateCodeSignatureFile( String outputFilePath, String inputFilePath,
                                         String developerId, ByteArray publicKey, ByteArray privateKey )

Generates a code signature file with the specified developer identifier and signing keys.

Parameter

Type

Description

outputFilePath String

Full path to the output signature file. Must have the .xsgn file extension.

inputFilePath String

Full path to the input file to be signed.

developerId String

The developer identifier.

publicKey ByteArray

The public signing key.

privateKey ByteArray

The private signing key.

This function is intended for signing JavaScript and shell script source code files (.js, .jsh and .scp extensions) without script identifiers. For signing identified executable scripts you should use the generateScriptSignatureFile function instead.

Important— The publicKey and privateKey objects should be securely wiped out as soon as they are no longer needed by calling the ByteArray.secureFill() method.


void Security.generateCodeSignatureFile( String outputFilePath, String inputFilePath,
                                         String keysFilePath, String|ByteArray password )

Generates a code signature file with developer identity and signing keys retrieved from a secure signing keys file.

Parameter

Type

Description

outputFilePath String

Full path to the output signature file. Must have the .xsgn file extension.

inputFilePath String

Full path to the input file to be signed.

keysFilePath String

Full path to the secure signing keys file. Should have the .xssk extension.

password

String or ByteArray

The protection password for the specified signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.

This function is intended for signing JavaScript and shell script source code files (.js, .jsh and .scp extensions) without script identifiers. For signing identified executable scripts you should use the generateScriptSignatureFile function instead.


void Security.generateLocalSigningKeysFile( String outputFilePath, String|ByteArray password )

Generates a secure signing keys file for the local signing identity.

Parameter

Type

Description

outputFilePath String

Full path to the output signature file. Must have the .xssk file extension.

password

String or ByteArray

The protection password for the newly created signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.


void Security.generateScriptSignatureFile( String outputFilePath, String scriptFilePath,
                                           Array entitlements, String developerId,
                                           ByteArray publicKey, ByteArray privateKey )

Generates a script signature file with the specified developer identifier and signing keys.

Parameter

Type

Description

outputFilePath String

Full path to the output signature file. Must have the .xsgn file extension.

scriptFilePath String

Full path to the input script file to be signed.

entitlements Array

An array of String objects representing the entitlements required for script execution. Can be an empty Array if no entitlements are necessary.

developerId String

The developer identifier.

publicKey ByteArray

The public signing key.

privateKey ByteArray

The private signing key.

Always specify the entitlements strictly necessary for execution of your scripts. As a good security practice, when possible try to write scripts not requiring any special entitlements.

Important— The publicKey and privateKey objects should be securely wiped out as soon as they are no longer needed by calling the ByteArray.secureFill() method.


void Security.generateScriptSignatureFile( String outputFilePath, String scriptFilePath,
                                           Array entitlements,
                                           String keysFilePath, String|ByteArray password ) )

Generates a script signature file with developer identity and signing keys retrieved from a secure signing keys file.

Parameter

Type

Description

outputFilePath String

Full path to the output signature file. Must have the .xsgn file extension.

scriptFilePath String

Full path to the input script file to be signed.

entitlements Array

An array of String objects representing the entitlements required for script execution. Can be an empty Array if no entitlements are necessary. Always specify the entitlements strictly necessary for execution of your scripts. As a good security practice, when possible try to write scripts not requiring any special entitlements.

keysFilePath String

Full path to the secure signing keys file. Should have the .xssk extension.

password

String or ByteArray

The protection password for the specified signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.


void Security.generateSigningKeysFile( String outputFilePath, String developerId, String|ByteArray password )

Generates a secure signing keys file for the specified Certified PixInsight Developer (CPD) identity.

Parameter

Type

Description

outputFilePath String

Full path to the output signature file. Must have the .xssk file extension.

developerId String

The developer identifier. Must be the identifier of a recognized CPD in the running PixInsight core application.

password

String or ByteArray

The protection password for the newly created signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.


void Security.generateXMLSignature( String filePath, String developerId,
                                    ByteArray publicKey, ByteArray privateKey )

Generates an XML file signature with the specified developer identifier and signing keys.

Parameter

Type

Description

filePath String

Full path to the file to be signed.

developerId String

The developer identifier.

publicKey ByteArray

The public signing key.

privateKey ByteArray

The private signing key.

The signature will be generated as a <Signature> top-level element. The signed data will be the canonicalized XML root element.

The specified file will be rewritten by this function to include the newly generated signature. If the file is already signed, the existing <Signature> top-level element will be replaced with a newly generated one.

This function is intended for signing update repository description documents (.xri file extension).

Important— The publicKey and privateKey objects should be securely wiped out as soon as they are no longer needed by calling the ByteArray.secureFill() method.


void Security.generateXMLSignature( String filePath, String keysFilePath, String|ByteArray password )

Generates an XML file signature with developer identity and signing keys retrieved from a secure signing keys file.

Parameter

Type

Description

filePath String

Full path to the file to be signed.

keysFilePath String

Full path to the secure signing keys file. Should have the .xssk extension.

password

String or ByteArray

The protection password for the specified signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.

The signature will be generated as a <Signature> top-level element. The signed data will be the canonicalized XML root element.

The specified file will be rewritten by this function to include the newly generated signature. If the file is already signed, the existing <Signature> top-level element will be replaced with a newly generated one.

This function is intended for signing update repository description documents (.xri file extension).


Object Security.getCodeSignature( String filePath )

Verifies the signature of the specified source code file.

Returns an object with the following properties:

Property

Type

Description

developerId String

The developer identifier. Can be an empty string if the signature is not valid, or if no signature exists for the specified file.

timestamp String

The signature timestamp in ISO 8601 format. Can be an empty string if the signature is not valid, or if no signature exists for the specified file.

valid Boolean

A flag true only if the signature exists and is valid for the specified file.

This function is intended to verify code signatures for JavaScript source files and (.js and .jsh extensions) and shell script files (.scp extension). For identified executable scripts you should use the getScriptSignature function instead.


Object Security.getScriptSignature( String filePath )

Verifies the signature of the specified script file.

Returns an object with the following properties:

Property

Type

Description

scriptId String

The script identifier. Can be an empty string if the signature is not valid, or if no signature exists for the specified script file.

developerId String

The developer identifier. Can be an empty string if the signature is not valid, or if no signature exists for the specified script file.

entitlements String

The entitlements required for script execution, as a comma-separated list. Can be an empty string if no entitlements are required, if the signature is not valid, or if no signature exists for the specified script file.

timestamp String

The signature timestamp in ISO 8601 format. Can be an empty string if the signature is not valid, or if no signature exists for the specified script file.

valid Boolean

A flag true only if the signature exists and is valid for the specified script file.

This function is appropriate to verify script signatures, that is, signatures generated for executable JavaScript and shell scripts with #feature-id or #script-id directives.


Object Security.getXMLSignature( String filePath )

Verifies the signature of the specified XML file.

Returns an object with the following properties:

Property

Type

Description

developerId String

The developer identifier. Can be an empty string if the signature is not valid, or if no signature exists for the specified file.

timestamp String

The signature timestamp in ISO 8601 format. Can be an empty string if the signature is not valid, or if no signature exists for the specified file.

valid Boolean

A flag true only if the signature exists and is valid for the specified file.

This function is intended to verify existing signatures in update repository information documents (.xri extension).


Boolean Security.haveEntitlement( String entitlement )

Returns true only if the specified entitlement is currently granted for the running script; returns false if the specified entitlement is not available.


Object Security.loadSigningKeysFile( String filePath, String|ByteArray password, Boolean publicKeyOnly = false )

Retrieves data from the specified secure signing keys file.

Parameter

Type

Description

filePath String

Full path to the secure signing keys file. Should have the .xssk extension.

password

String or ByteArray

The protection password for the specified signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.

publicKeyOnly Boolean

If true, only the public signing key will be retrieved. This is useful for applications where only public keys are required, avoiding the unnecessary decription and storage of private keys.

Returns an object with the following properties:

Property

Type

Description

developerId String

The developer identifier.

publicKey ByteArray

The public signing key.

privateKey ByteArray

The private signing key. Will be an empty ByteArray if the publicKeyOnly parameter is true.

valid Boolean

A flag true only if the signing keys file exists and is valid.

Some or all of the above properties can be empty strings if the signing keys file is not valid.


void submitCertifiedDeveloperData( String developerId, ByteArray publicKey, String contactEmail
                                   [, String publicEmail[, String url[, String name[, String info]]]] )

Creates and sends a Certified PixInsight Developer proposal, or CPD submission, with the specified developer identifier and public signing key.

Parameter

Type

Description

developerId String

Developer identifier (case-sensitive). Required.

publicKey ByteArray

The public signing key. Will be wiped out automatically by this function. Required.

contactEmail String

An email address where the developer can be contacted. Required.

publicEmail String

An email address that will be included in the CPD database distributed with PixInsight. Optional.

url String

The URL of a website or server where the developer distributes her or his work. Optional.

name String

The actual developer's name.

info String

Additional information describing the developer's work. Optional

The developer's identifier, public signing key and identifying data will be sent to our corporate server as a POST request. The destination URL is currently:

https://pixinsight.com/security/cpd-submit.php

All CPD submissions are stored in our databases and notified through internal email messages to our staff. New CPD identities are added to the certified developers database and distributed as updates to the PixInsight core application on all supported platforms.


void submitCertifiedDeveloperDataWithSigningKeysFile( String keysFilePath, String|ByteArray password,
                                             String contactEmail
                                             [, String publicEmail[, String url[, String name[, String info]]]] )

Creates and sends a Certified PixInsight Developer proposal, or CPD submission, with developer identifier and public signing key retrieved from a secure signing keys file.

Parameter

Type

Description

keysFilePath String

Full path to the secure signing keys file with the CPD's identifier and public signing key. Should have the .xssk extension.

password

String or ByteArray

The protection password for the specified signing keys file. If a ByteArray object is used for this argument, its contents will be securely wiped out, ensuring that no copy of the specified password will be left in memory after calling this function.

contactEmail String

An email address where the developer can be contacted. Required.

publicEmail String

An email address that will be included in the CPD database distributed with PixInsight. Optional.

url String

The URL of a website or server where the developer distributes her or his work. Optional.

name String

The actual developer's name.

info String

Additional information describing the developer's work. Optional

See the Security.submitCertifiedDeveloperData function for more information.


Boolean validPassword( String|ByteArray password )

Checks if the specified password is valid and can be considered reasonably secure. Returns true if and only if the specified password:

  • Contains 8 or more characters.
  • Does not contain white space characters.
  • Contains at least one lowercase letter.
  • Contains at least one uppercase letter.
  • Contains at least either one decimal digit or one punctuation sign.

The standard SigningKeys script calls this function to validate passwords for creation of new signing keys files.

10.1 Secure Management of Passwords and Signing Keys

In the methods of the Security PJSR object that we have described above, passwords can be specified either as standard String JavaScript objects or as ByteArray PJSR objects. Literal strings are easy to use, but besides the obvious fact that storing passwords verbatim in scripts may not be the best security practice, especially if you are not working on a trusted machine, JavaScript strings pose an additional risk: multiple copies of the passwords may remain in memory during the lifespan of the application, and even written to swap space, or to core dumps. This happens because we have no direct control on the way strings are allocated, deallocated and copied internally by the JavaScript engine.

In contrast to JavaScript strings, a ByteArray object keeps its data stored in a native object on which we have total control (more specifically, an instance of the pcl::ByteArray C++ class), so we are able to guarantee a much more secure behavior. If a password is specified as a ByteArray object, the PJSR implementation guarantees that all instances of the password will be securely wiped out as soon as the password has been used for the corresponding encryption/decryption task, leaving no traces in memory. The same applies to public and private signing keys, which are always stored as ByteArray objects.

Since version 1.8.9-1 of PixInsight, the ByteArray object has new methods that facilitate the secure management of sensitive data, which we describe below.


ByteArray ByteArray.fromByteValues( [uint8 b1[, ..., uint8 bn]] )

Creates a new ByteArray with contents specified as a sequence of byte values b1,...,bn, where each value must be in the 8-bit range [0,255].

For example, instead of writing a password literally as plain text, such as:

let keys = Security.loadSigningKeysFile( "/path/to/keys.xssk", "1Ngh%T97Y|Q@" );

we can use the following code:

let keys = Security.loadSigningKeysFile( "/path/to/keys.xssk",
         ByteArray.fromByteValues( 49, 78, 103, 104, 37, 84, 57, 55, 89, 124, 81, 64 ) );

which is not so obviously detectable as a password. More importantly, as we have explained above, the ByteArray object will wipe out its contents securely once the signing keys file has been loaded, generating no copies and leaving no traces of the password in memory.


void ByteArray.secureFill( [uint8 value = 0] )

Fills the entire contents of this ByteArray securely with the specified value (zero by default). The implementation of this method does not use the implicit data sharing mechanisms that normally apply when an object is modified, leading to generation of a duplicate object (or deep copy) to preserve the original (also known as copy-on-write). Instead, this method guarantees that all existing objects sharing the same allocated block of memory will be affected equally by the filling operation, so no copy of the original data (password or signing key) can exist after calling this function.

A typical use of this method in the context of code signing is shown in the following example:

let keys = Security.loadSigningKeysFile( ... );
Security.generateScriptSignatureFile(
            "/path/to/file.xsgn", // outputFilePath
            "/path/to/file.js",   // scriptFilePath
            ["com.pixinsight.security.pcl.experimental-features"], // entitlements
            "PTeam",              // developerId
            keys.publicKey,
            keys.privateKey );
keys.publicKey.secureFill();
keys.privateKey.secureFill();

In this example, the two final calls to ByteArray.secureFill() guarantee that no copies of the signing keys will be present in memory after the signature has been generated.

10.2 The Problem of Typing in Passwords

The Edit PJSR object provides a single-line text field, commonly used to enter string parameter values in all dialog windows and other controls defined with PixInsight's JavaScript development framework. When the Boolean Edit.passwordMode property is set to true, the control hides all password characters as you type them. Setting Edit.passwordMode to true also guarantees that no copies of the password will remain in memory when the object is destroyed, similarly to ByteArray, as we have described above.

However, reading the Edit.text property requires generation of a String JavaScript object, on which we cannot have any control, as we have explained. So if you use an Edit control to acquire a password:

this.password_Edit = new Edit( this );
this.password_Edit.passwordMode = true;
...

and then access its text property to get the actual password characters:

let password = this.password_Edit.text;

then there could be copies of the password left in memory after script execution. Note that this will also happen if you declare an event handler receiving text from the Edit control:

this.password_Edit.onTextUpdated = function( text )
{
   // do something with text
};

So we have a potential security issue here. Of course, this has been solved since version 1.8.9-1 of PixInsight with the introduction of a new property:


ByteArray Edit.utf8

Returns the text of the Edit control encoded in UTF-8 as a new ByteArray object. No JavaScript String object is generated. This property guarantees that no copy of the internal C++ object used to store the string will be generated, and that the returned ByteArray object uniquely references its allocated memory block.

Thanks to this property you can use Edit controls to acquire passwords securely. The following fragment would be used to let the user enter a password and load a signing keys file:

this.password_Edit = new Edit( this );
this.password_Edit.passwordMode = true;
...
let password = this.password_Edit.utf8;
let keys = Security.loadSigningKeysFile( "/path/to/keys.xssk", password );
Security.generateScriptSignatureFile(
            "/path/to/file.xsgn", // outputFilePath
            "/path/to/file.js",   // scriptFilePath
            ["com.pixinsight.security.pcl.experimental-features"], // entitlements
            "PTeam",              // developerId
            keys.publicKey,
            keys.privateKey );
keys.publicKey.secureFill();
keys.privateKey.secureFill();

Note that the call to Security.loadSigningKeysFile() will automatically wipe out the contents of password. The last two calls to ByteArray.secureFill() wipe out the signing keys. Nothing sensitive can be left in memory using this technique.

10.3 Preventing Attached Processes and Memory Dumps

On UNIX and Linux platforms, the ptrace system call[13] can be used to control and inspect running processes. This mechanism is used by debugger programs, such as gdb, which are basic software development and analysis tools. As you probably know or can figure out, nothing prevents using the ptrace system call to implement an exploit that gathers sensitive information, such as passwords and signing keys, by attaching a process to a running application such as PixInsight.

Although this potential risk is very unlikely to be a real threat in practice, especially if the application runs on trusted and controlled machines and networks, since version 1.8.9-1 we have a command-line argument that can be specified to prevent other processes from attaching to the PixInsight core application:


--no-attach

When PixInsight is executed with this command line argument, it prevents other processes from attaching to the running PixInsight core executable. Generation of core dumps is also disabled on Linux.

On Linux, the prctl syscall[14] is invoked with the PR_SET_DUMPABLE flag set to zero. On macOS the ptrace system call[15] is invoked with the PT_DENY_ATTACH request. On Windows this argument has no effect.

This command-line argument works on Linux and macOS exclusively. There is no way to prevent the attachment of processes on Windows.[16]

11 Utility Scripts and Commands

[hide]

Version 1.8.9-1 of PixInsight introduces a set of utility scripts and menu commands that greatly facilitate all tasks involved with our code security system. In this section we describe these items with practical usage recommendations.

11.1 The SigningKeys Script

Generate Signing Keys

With this option, the SigningKeys script can be used to create a new pair of private/public signing keys, which will be stored as a secure signing keys file (.xssk extension). Note that the same script can be used to generate keys files for CPDs and local developers.

Export Signing Keys

This option is useful to generate a duplicate of an existing signing keys file, with the possibility of using a different protection password. The duplicate file will contain the same private and public keys as the original.

Output

In this section you specify the output file, which must carry the .xssk extension, and its protection password. Please keep this file in a safe place and don't forget its password.

Important— The necessity to use very strong passwords to protect .xssk files cannot be overemphasized. Keep in mind that a stolen signing keys file, if the thief manages to guess its protection password, can be used to supplant your identity, with possible catastrophic consequences for both you and PixInsight.

11.2 The SubmitCPD Script

To become a Certified PixInsight Developer (CPD) you have to send us a proposal by submitting your public signing key, along with your developer identifier and other identifying data, to our corporate server. The standard SubmitCPD script facilitates this task. On this script you can specify the following items:

If the Use keys file option is selected

Keys file

The secure signing keys file (.xssk extension) with the public key that will be submitted. You must have generated this file with the standard SigningKeys script.

Important— Only your public signing key will be retrieved from the specified signing keys file, and then it will be decrypted using the password entered in the corresponding field (see below). The private signing key won't even be read from the .xssk file.

Password

Secure signing keys files are password-protected. Enter here the password corresponding to the keys file that you have specified.

If the Enter id/key data option is selected

Developer identifier

Your developer identifier. Must be exactly equal to the identifier that you specified when you created your signing keys file with the SigningKeys script. Developer identifiers are case-sensitive.

Public key (hex)

Your public key in hexadecimal format. A public signing key has 32 bytes, which in hexadecimal format requires 64 characters. You can get your public key with the Security.loadSigningKeysFile function. You can use the ByteArray.toHex function to get the key in hexadecimal format.

Contact email

A working email address where we can contact you, if necessary, in relation to your CPD data submission (required). This email address will be kept private; it will never be distributed with the PixInsight core application.

Public email

Your public email address (optional). This email address, if specified, will be included in the CPD database distributed with the PixInsight core application.

URL

The URL where you provide information about your development work. This item is optional, but highly recommended.

Name

Your name, or the name of your company, so your users can identify you. This item is optional, but highly recommended. If desired, you can enter multiple text lines in this field.

Information

Provide additional information about you and your work (optional). If desired, you can enter multiple text lines in this field.

Note— Use the Enter id/key data option to enter your developer identifier and public signing key manually, in case you don't trust us to retrieve your public key from a signing keys file. Obviously, the Use keys file option is easier and much less error-prone.

When we receive your proposal we'll study it and, in case we need additional information, we'll contact you at the specified email address. Once approved, your CPD data along with your public signing key will be included in the CPD database, which will be distributed as an update for all supported platforms. Your code signatures will be recognized and verifiable as soon as the update has been deployed.

11.2.1 If you change your signing keys

Important— Once you have submitted your CPD identity, you must be careful not to change or lose your signing keys. Since the PixInsight core application distribution will include your data in its CPD database, if you change your keys and generate new signatures, those signatures won't be valid for existing PixInsight installations until your new public key is included in the CPD database and distributed as an update. Similarly, once your new public key is known to PixInsight, you should update all signatures for your scripts with the new signing keys.

If you lose your signing keys or have to generate new ones for some reason—for example, because you forget your signing keys file's protection password—, then you should perform a new CPD submission with the SubmitCPD script. The new signing keys file must be generated for the same developer identifier, so we can know who you are when we receive your new data.

Please don't hesitate to contact us if you have to change your keys. In these cases we'll be glad to assist you with the best procedure to minimize disruption in the distribution of your code.

11.3 The CodeSign Script

The CodeSign script allows you to generate script and repository signatures with your CPD or local signing identity. The following file types can be signed:

JavaScript source code files (.js extension)

You must specify the executable .js file, with a valid #script-id or #feature-id directive, for each script. Note that the rest of source code files belonging to a script, such as .jsh and other .js files included by means of #include directives, should not be signed. Signing them is useless and considered bad practice, since their signatures will never be verified, and their presence can be misleading.

JavaScript include files (.jsh extension)

.jsh files are typically used with #include directives. You should not need to sign this type of files, for the reasons explained above.

Shell script files (.scp extension)

These files are executed as sequences of command-line commands. The most known example is the standard startup.scp script, which is loaded and run each time PixInsight is executed. You rarely should need to sign files of this type, since PixInsight scripting development is almost always done entirely in the JavaScript language.

Repository information documents (.xri extension)

The updates.xri file that enumerates and describes the updates available in a PixInsight update repository should always be signed with a valid CPD identity. This is essential to guarantee the trusted origin and authenticity of all PixInsight updates.

Along with the list of scripts and/or repository information documents to be signed, the CodeSign script requires the following parameters:

Keys file

Specify the secure signing keys file (.xssk extension) with your private and public keys.

Password

.xssk files are password-protected. Enter the password required for the specified signing keys file.

Entitlements

The security entitlements required for the script(s) that will be signed, or empty if none is necessary. Each entitlement must be written in a single text line.

11.4 The Script > Local Signing Identity Command

As we already have described, the local signing identity allows you to generate script signatures without needing to be a CPD. These signatures are only valid on machines where you have valid license files for your PixInsight software license. When you select the Script > Local Signing Identity... main menu option, the Manage Local Signing Identity dialog is executed, as shown above. This dialog provides thefollowing items:

Keys file

Select the secure signing keys file (with the .xssk extension) which provides the public key that will be associated with the local signing identity. You should have generated this file with the standard SigningKeys script, with its Local signing identity option enabled.

Password

.xssk files are password-protected. Enter the password required for the specified signing keys file.

Make the local signing identity persistent

If checked, the local signing identity will be made persistent across application executions by storing its encrypted public key in global settings. The corresponding public key will be loaded upon application startup.

Remove the local signing identity

If this option is checked, the local signing identity will be removed if the dialog is accepted. If the identity was made persistent, its public key will also be removed from application settings, so it no longer will be available in successive sessions.

11.5 Security Preferences Options

Since version 1.8.9-1 of PixInsight the standard Preferences process includes a new Security section with the following options:

Allow execution of unsigned scripts

Allow execution of scripts without code signature files. Enabled by default.

Note— Execution of scripts with invalid code signatures is always forbidden, regardless of this option.

Allow unsigned update repositories

Allow downloading update packages from update repositories that don't provide signatures. Enabled by default.

Note— Downloading update packages from repositories with invalid signatures is always forbidden, regardless of this option.

Allow insecure update repositories

Allow update repositories using insecure network protocols (HTTP or FTP). Enabled by default.

Authenticity of downloaded update packages cannot be guaranteed when insecure connections are used, which is a serious security risk. The HTTPS and FTPS protocols are strongly recommended and will be mandatory in a future version of PixInsight. Use of insecure connections is still allowed, but at your own risk.

If this option is disabled, only the secure HTTPS and FTPS protocols will be allowed.

Report script signatures

Write information about valid script signatures on console upon script execution. Enabled by default.

Warn on execution of unsigned code

Ask the user for authorization to execute scripts without code signatures, and write the corresponding warning messages on console when unsigned scripts are executed. Disabled by default.

Note— To ease adoption of the new security system, for now this option is disabled by default. This option will be enabled by default in a future version of PixInsight.

Enable the local signing identity

Allow creation and use of the local signing identity. Enabled by default.

11.6 The lscpd Command

The lscpd command allows you to generate plain text reports with information acquired from the certified developers database. This command is available since version 1.8.9-1 of PixInsight. Here is the output of lscpd --help:

lscpd --help
The lscpd command writes a report with information about Certified PixInsight
Developers (CPDs).
Usage: lscpd [<arg_list>] [<pattern_list>]

<pattern_list>

      Is an optional sequence <pattern> [<pattern_list>] used to select a subset
      of CPDs with multiple wildcard patterns.

<pattern>

      Is a wildcard pattern for selection of CPD records. The standard wildcard
      characters '*' and '?' are supported. CPD records are selected by
      searching in developer identifiers, unless the -n argument is used.

-n | --name

      Apply <pattern> to developer names instead of developer identifiers.

-d=<data_spec> | --data=<data_spec>

      Defines the information items included in the generated report.
      <data_spec> can include one or more of the following characters

      i  developer identifiers.
      e  email addresses.
      u  website URLs.
      n  developer names.
      I  additional information.
      k  public signing key (hex format).
      *  all items.

--help

      Displays this help and exits.

Without <pattern_list>, lscpd prints information for all CPD entries found in
the current certified developers database.

If no items are selected with the -d argument, lscpd prints all information
items except public signing keys for the set of selected developers.

References

[1] Wikipedia article: Public-key cryptography

[2] Daniel J. Bernstein, Niels Duif, Tanja Lange, Peter Schwabe, Bo-Yin Yang. High-speed high-security signatures. Journal of Cryptographic Engineering 2 (2012), 77–-89.

[3] Website: Ed25519: high-speed high-security signatures

[4] Wikipedia article: SHA-2

[5] GitHub repository: Portable C implementation of Ed25519, by Orson Peters

[6] Website: SUPERCOP toolkit

[7] Linux manual page: random(4)

[8] Microsoft Windows Development Documentation: BCryptGenRandom function

[9] Wikipedia article: Advanced Encryption Standard

[10] Wikipedia article: ISO 8601

[11] Wikipedia article: UTF-8

[12] Wikipedia article: Reverse domain name notation

[13] Linux manual page: ptrace(2)

[14] Linux manual page: prctl(2)

[15] Mac OS X manual page: ptrace(2)

[16] Stack Exchange / Information Security question: Why does Windows allow non-parent processes to read/write the memory of a process?