Signed Scripts

Last modified by Vincent Massol on 2024/02/26 17:53

 XWiki
 Implementation
 Dormant
 
 
 

Description

Objectives

Signed scripts should be the cure to security issues and limitations currently encountered when some piece of code should be executed with unrestricted access (aka Programming Rights or PR). The general idea is to sign any scripts, that require PR, to securely associate them with the privileged user that have written them.

Cryptographic reminder

If you are fluent with cryptographic terms, go ahead to the next section, but if you are not so sure, here is a very quick reminder of important cryptographic concept used in this proposal.

Private keys, Public keys and Passphrases

To encrypt data, a mathematical formulae (a Cypher) is used and base its algorithm on some secrets. Symmetric encryption use the same secret to encrypt and decrypt data while asymmetric encryption use a different secret to encrypt and decrypt the same data.

To provide secure asymmetric encryptions, keys are used in pair, one of the key is called the private key, and the other one is the public key. The private key should be kept secret and store securely, while the public key may be publicly disclosed, so anyone wanting to authenticate or communicate with the private key owner could do so with confidence. Data encrypted with the private key could be decrypted with the public, and vice versa. This allow secure communication over a public channel.

To secure the private key, a passphrase may be used. A passphrase is simply a human friendly cryptographic key, it is like a password, but usually larger. Using a symmetric encryption cypher, the passphrase allows encrypting and decrypting the private key.

Message digests and Signatures

To verify the integrity of a large amount of data, you first create a shorter version of that message, which is called a message digest. A message digest is basically a large computed number (a large hash) that represent the initial message. Algorithm used to compute a message digest are made so that it is very difficult to have two messages sharing the same key, and almost impossible from the digest to create a corresponding message.

Using an asymmetric encryption, the message digest is encrypted by the private key of a signer, and the result of this encryption is joined with the signer certificate (containing its public key and some information about signer, see below) to form a Signature. The signature provides both data integrity and authentication of the signer. The signature could be verified using the signer public key (contained in the certificate) and comparing the decrypted message digest, with the message digest computed from the original data.

Certificates, certificate chains and Root CA

Since a public key is just a cryptic text, you have not any clue to know who have published that key, and without knowing the owner for sure, any authentication aspect could not be achieved. Certificate simply contains a private key with some metadata to identify the public key owner, but also to identify the certificate issuer. The certificate issuer is the one who have signed the certificate to ensure its integrity, and who you trust to have properly identified the public key owner. 

Verifying the certificate signature will provide to you the certificate of the issuer. This one being sign by another issuer, that you may verify in the same way. This create a certificate chain that will go up to the certificate of a Root Certificate Authority (Root CA). A root CA has is certificate self-signed, so you can verify its integrity, but you cannot authenticate it automatically. To create trust, the user will list the Root CA she trusts (she have check by manually), and the application will blindly trust those certificates.

Current situation

Current implementation

Any script or macro, that is not executed in a sandbox (most scripts except velocity, and groovy under a Secure Groovy Customizer) or that want to access privileged API, needs to receive Programming Rights to be allowed to run properly.

Currently, those programming rights are granted if one of the following is true:

  • a global user is logged in, and a security rule at the farm level (XWikiGlobalRights objects in XWikiPreferences) allows the PROGRAM right to her.
  • the content_author of the current context document is a global user and a security rule at the farm level (XWikiGlobalRights objects in XWikiPreferences) allows the PROGRAM right to her.

Documents may also be tagged to be document requiring PR, by adding a RequiredRightClass object to them. Discrepancies between those requirements and granted rights may be fixed through an administrative UI by privileged users.

Script may drop privileges using the dropPermission API to prevent privileged code to leak their privilege to unprivileged one, or privileged user to execute unexpected code in privileged mode.
 

Current issues

  • Scripts requiring PR suddenly stop working when an unprivileged user save the document
  • Not all document containing scripts or macro requiring programming right are tagged properly, so the list of documents being broken is inaccurate
  • When a script has been broken by a unprivileged user, the task to fix it is, at best, definitively not intuitive, and, at worse, totally insecure.
  • Privileged users may be easily tricked to render content with PR, or to save some content with PR
  • PR of a document may leak to included documents or any other content rendered in the context of that document
  • Deleting a privileged user cause all documents she have saved to immediately loose their PR.
  • Installing extensions or importing documents requiring PR needs to be done by a privileged user and to also preserve document authorship
  • ... (including some undisclosed issues)

Current limitations

  • Only global users may receive more privileges and their is only one level of elevated privileges.
  • The whole document receive PR (unless dropPermission is used, which is also a one way operation), this violate the least privilege security motto.
  • Granting and revoking PR rights is not really flexible, nor easy, in particular when the content is imported.
  • Once dropped, privileges cannot be granted again in the course of a document.

Potential benefits

Using a signature mechanism to sign macros (and therefore scripts) may provide the same benefits that SSL provides to websites.

Integrity

Signatures ensure that the macro content (aka the script code) has not been tampered between the moment a privileged user has allow PR on it, and the moment it is about to be executed with those elevated privileges. 

The same integrity could be extended for ensuring a simple script (non-PR), or even any part of a document (using a macro to mark it) has not been tampered during edition of any other part of that same document.

Authorship

The relation between the script author, who has decided to grant elevated privilege to the macro and the macro itself could not be tampered. Moreover, the authorship could be extended to user outside of the wiki, in particular to extension authors, template authors, and exported document authors. The life of the users may be totally unrelated to the life of certificates used and trusted for signatures.

This authorship may be extended for other purposes than script, allowing any part of document to be signed for approval.

Web of trust

By using certificate, and certificate chain, a web of trust could be built:

  • intermediate CA can be used to provide more granular or contextual privilege escalation
  • extension author, other wiki author can be trusted individually or globally using trusted CA
  • Certificate Revocation List can be supported to revoke certificate in a granular way
  • (extended usage) some privileged users could delegate privileges to other users, by signing their certificate, they could as well split their own certificates to provide easier revocation of their work, ...

Potential drawbacks

Persistence

Once PR has been granted to a given script by signing it, this signature could only be revoked by revoking the certificate used to sign it. A new version of the same script does not prevent older version to stay signed and keep their privileges. Signers should be really careful to use certificate that could be revoked appropriately when an issue is encountered within one of their signed scripts.

Context manipulation

Signed scripts should be careful to not be tricked by context manipulations and should check their inputs carefully. Velocity scripts in particular are vulnerable to velocity context corruption, and a solution should be provided to allow those scripts to trust some important context variables (like $doc or $xwiki). Care should also be taken to invalidate signature when the script is taken out of its context (put some context with the signed content).

Complexity

While possibilities are endless using certificates and key pairs, the complexity for the end user should be kept to a minimum. Users wanting simplicity should not be annoyed by this new feature, even if the security level of the solution gets degraded for them.

Design proposal

This proposal try to be as modular and extensible as possible. This is a very important aspect. Scripts are the core of a XWiki site, so once a solution applied and an old wiki migrated to that improved solution, we should be able to guarantee that further evolution of the security model will not suddenly break an existing installation. Even more, current existing installations should be as easy as possible to migrate, but we should not constraints the solution because an automated migration will not be possible.

To achieve those goals, the implementation will separate the following concepts as much as possible:

  • Integrity checks: process of checking a signature, and extracting the certificate used to sign
  • Authorship attribution: process of associating certificate with users or organisation, and constitution of a web of trust
  • Authorization: process of attributing enhanced privilege to a piece of code that have passed the integrity check and that have been signed by a given certificate
  • Execution : process of controlling the execution of the code based on its authorizations

Implementation

Cryptographic signature

Since the crypto API has been built with the idea to apply it to signed script, and moreover it is heavily based on widely used standards (x509 certificate and PKCS#7 signatures), it is probably our best choice to provide all the machinery needed to sign and check signatures. Since the API has not been used much, it should be at least audited and may need some improvement.

Signatures are stored as a PKCS#7 signed data with embedded X509 signer certificate (limited to one signer per signature) and detached content (no reason to duplicate it).

To generate signatures, the crypto API requests a key pair, a passphrase and either a content to sign or a digest of it. This content or digest should take care of including some contextual information, like the reference of the container of the macro being signed.

To validate signatures, the crypto API requests a signature and either the content signed or a digest of it. On success it returns the public certificate of the signer. This public certificate should later be checked against a list of trusted certificates, working through the certificate chain as needed.

Signature storage and link to macro

Including the signature with the macros content itself could be the source of many syntactical issue, and clearly not generic. Adding the signature to macros parameters could be inconvenient for users, and difficult to be automated. Modifying the document content is not appropriate, so the signing process should work without any alteration to the content.

Therefore, Signatures are stored as XObject inside the document containing the macro signed. This allows easy collection of existing signatures from an administrative UI for migration purpose and global review.

The macro and signature are detached so we need a way to link them together. Since the verification of signature could be memory and CPU intensive, we also need a way to cache result of the authorization process for a given macro having a verified signature. Such caching requires to reference both macro, and the certificate of the signer, if any of those are altered, the cache should be invalidated. Reusing the actual security cache used for authorization is therefore a really interesting option to be investigated.

So, we need to create EntityReference for macro. Each Block of our XDOM should be possible to reference. Even if only some macros support to be signed, having a generic solution to reference blocks could benefit other features. 

For the our purpose, this reference should not have to be really persistent, since any change in the document invalidates the cache and may therefore invalidate the reference as well. However, if the change has not impacted the content signed, relinking the signature properly is required. The important is that this entity reference should have its container (object reference or document reference) as a parent.

TODO: Define the best way to compute those references

Key pair and corresponding Certificate storage

For the proper storage of key pairs used for signing, in particular private keys, the level of trust may greatly vary. There is several possibilities, some are listed here in decreasing level of security and increasing usage simplicity:

  • The key pair (and the certificate) is kept with a passphrase encrypted private key on the computer of the developer. The developer signs scripts by hand, or by using an appropriate maven plugin during a special build phase, and should enter its passphrase interactively.
  • The key pair (and the certificate) is kept with a passphrase encrypted private key either on the server or even better in the browser of the developer. A javascript based signing process is used to sign content transmitted by the server.
  • The key pair (and the certificate) is kept with a passphrase encrypted private key on the server. The user provide its passphrase to the server when the server need it to generate a signature.

Of course, these case may use a trivial or empty passphrase, this does not matter on a technical point of view, but this will clearly lower the trustiness of the corresponding certificate.

Apart in Firefox, Solution B may be complex to put in place and is not planned on the short term. Solution A is available by hand, and a maven plugin should probably be envision in the future. 

While providing the lowest security, solution C will be our first attempt. First, because it allows us to keep it very simple for the end user. The worse drawbacks of that solution could be mitigated over time without disrupting the solution. 

For the sake of simplicity, our first implementation will store key pairs (and the certificate) in user profile document. This is probably not a very safe place, but the private key is encrypted with a passphrase, and this is definitely not the worse aspect of this proposal in term of security.

Building trust

To obtain a separation between the key pairs and their certificates and the trust of a signature, as well as to provide a simple solution for end users, we need to implement a minimal PKI infrastructure in XWiki. Usually a valid certificate is obtained by having it sign by a recognized Certificate Authority (CA), which usually is done for a fee. We do not want that constraint for some local script in a wiki that just need PR.

Each Wiki installation will generate its own CA (key pairs and self-signed certificate). Since certificates and signing could be used for many other kind of usage, and to be open to extension, an intermediate CA is also created to hold the privileged users, and, why not, another one for any other users. The privileged user intermediate certificate is trusted for managing privileged API access. Each user of the wiki will be able to request the generation of its own private key, and request for its certificate to be sign depending on their access. The admin will have to proceed to such signature.

Option: The key pair of the Root and intermediate CA local to the server are not be encrypted by a passphrase to allow the automated signature process of user certificates based on users rights. These are not stored in the wiki, but on the FS to improve a little bit the security.

To properly support delegation for subwiki users, we may imagine later to create intermediate key for those subwiki as well. However, providing unrestricted access to subwiki user seems like non-sense until we have the ability to create some sort of sandbox limiting access to a subwiki for an less restricted API.

Trusting Distribution and Extensions

For XWiki Committers, a XWiki CA could be created to hold some intermediate certificates. For easier versioning of our work, we will probably prefer to use intermediate CA for each major (and even minor) version of our distribution. This ease the revocation of an older script when a security hole is discovered.

To distribute those certificates, a new type of extension is handled to deploy those certificates appropriately by a simple extension dependency.

Other extension authors may proceed in the same way.

Import from a trusted wiki

Importing document from a trusted wiki requires to also add the trusted wiki certificates into the current wiki (CA and intermediate CA).

Trusted certificate

While we want the storage implementation to be an implementation detail to permit future extension, probably the easiest way to integrate list of trusted certificates into XWiki, will be to store them in XObjects. While the security may be seen lower than using a true keystore, a properly protected space containing a single certificate chain per document should work effectively like a nice keystore. Another benefit is that we may easily create several keystore, at any level, including subwikis to provide granularity to the authorization process. And we may easily interrogate several keystores at once when searching for a given certificate.

TODO: Define more precisely where we store these XObject and how we define the link to the privilege provided

Contents

We should consider that signing could be used for many different purposes and not all signable macros should systematically require a signature. Therefore, each signable macro will have to decide for itself if a signature is needed. We may have a {{sign}} macro that always require a signature but a {{script}} macro that only require a signature when the script itself require PR, or the script author has explicitly request for it to simply ensure the integrity and authorship of that script.

Script macros

The script macros should be signable. A signed macro may receive elevated privilege, but we also want to allow privileged users to sign macro only for integrity purposes. For script that may run with elevated privileges, a new macro parameter will be introduced. Something like {{script security="unrestricted"}} could be an appropriate way to declare that this script is written for elevated privileges. It declare the intention of its author and imply its responsible engagement to check inputs and context appropriately. The default value would be {{script security="restricted"}} for script that may be run without elevated privileges and {{script security="unrestricted"}} for those that always require privilege elevation (scripting languages that require PR). Another argument would be used to simply request signature, like {{script integrity="check"}}. The default value for this second argument is "none" when the security is "restricted", and "check" when security is "unrestricted".

Sign macro

A new {{sign}} macro could be use to sign any part of a document to ensure its integrity. This macro always requests to receive a valid signature.

Nesting signable macros

This is not an easy use case, both from a user POV, but also from a technical POV. We may imagine that user want their signature to be considered on nested macros. 

TODO: But what happen if the nested macro was already signed (by another user) ?

Nested document

Document included inside a signed macro are not considered part of the signature, and the context of those inclusions are independent of the context of the including document for signing aspect.

Interfaces

Key management and certificate signing

User interface

A user should be able to request the generation of a new key pair from its user profile, and create a Certificate Signing Request to have its certificate signed by the local Root CA.
Its user profile will store and display its Certificate and key pair, and display their status.

Administrative interface

The administrator of a wiki will need to manage certificate and keys on their wiki. The task includes:

  • Initial Root CA and intermediate CA creation and protection by a passphrase
  • Signing user certificates waiting to be signed
  • Revoking user certificates
  • Deleting user key/pair and certificates

Saving a document

The signature UI is triggered during document save action, following these steps:

  1. First the document is saved, without processing signature, ensuring editing operation is properly achieved. During this save operation, if any existing signature could be reuse, it is properly relinked to stay with their related content; if any useless signature (no request to be signed, or broken one) is found, it is removed; and finally, if any macro require a signature and is not satisfied, a new empty signature XObject is created.

 2. If the user is able to sign (let say, have a key pair), has allowed this save operation to propose signing (see next) and some empty signature object has been found, the Document Signing Interface (DSI) is launch.

The save operation could be a Save&View or Save&Continue one, and both have the ability to say that you would like to sign as well, or not. This could be through multi-choice button with clever default, or a checkbox like the minor save feature, or whatever depending on the skin.

Document Signing Interface (DSI)

If the user is able to sign (let say, have a key pair), she could access the DSI for a given document. Depending on the way it is called, the DSI proposes only to sign macros that have requested a signature but has not received one (empty signature XObject), or all macros that have requested a signature (all signature XObject available). The user (only admin?) is able to change that default mode interactively. For each signature, the corresponding content to be signed is retrieve from the document and available for review in source mode (and WYSIWYG mode?). The user is allowed to sign (and re-sign) those requests individually or as a whole, partially or fully, just by providing her choices and her passphrase.

The passphrase is securely sent to the server encrypted to avoid clear passphrase transmission over the network, even when SSL has not been enable. To that purpose, and any similar purposes, a key pair has been generated on the server at startup. 

For avoiding annoying repeated passphrase requests, the passphrase could be cached (as securely as possible) for a couple of minutes after their last usage, on the user browser, allowing a new call to the DSI to reuse it in the same user session. This feature could be deactivated at the passphrase prompt, or more permanently in the user profile.

Status and Source view

Document should display their signing status when displayed. From this status, depending on the situation, the DSI could be triggered for the document. 

Since signatures could be used for simple integrity checks, we also need a source view to show what is currently properly signed and who sign it, and what is expected to be signed and is not. It could also display information about the signer and its status.

If possible, this could be extended to the WYSIWYG editor, showing currently properly signed macro, until you made a modification to it.

List of documents requesting signatures

Since the save operation extracts and prepares signature objects for all macros requesting a signature, we may easily provide a more centralized UI for displaying the list of document having some signature requests unsatisfied. A direct access to the DSI for each of them allows to fix each one individually.

Migration

Migrating existing users scripts that require PR to use the new signature mechanism will not be really easy. It could be seen like trying to fix security holes while keeping them open ! This is why we do not believe that a fully automated migration process could provided. That said, we cannot afford the breakage of existing wikis either.

The actual Programming Right solution will be deprecated, and even deactivated by default in the distribution. The PROGRAM right will be deprecated and the default admin users will not receive it anymore.

A configuration switch will allow to keep the existing PR solution active. This configuration will be kept until the next major release.

Users will be encouraged to migrate their existing script progressively. To do so, even when the backward compatibility switch is active, they may create a user that do not have PROGRAM right, and use it to save existing PR document, than fix the scripts in them using the signature mechanism. Once the users with PROGRAM right do not own any document containing scripts, the migration process will be accomplished. To help achieving that process, an extension could be provided, that list those documents.


 

Get Connected