JavaScript Security in Mozilla
Page Maintained by John TaylorThe component security Terminology page gives a general explaination of many of the terms on this page. This document describes the security model used in Mozilla and provides information on how you can create signed JavaScript applications which can access expanded privileges.
There are two security policies in JavaScript:
- The same origin policy is the default policy. It dates from Navigator 2.0, and Communicator 4.x.
- The signed script policy is similar to that of Communicator 4.x. This policy for JavaScript is based upon the Java security model, called object signing. To make use of the new policy in JavaScript, you must use the JavaScript privileges API and then sign your JavaScript scripts.
This document is intended for JavaScript programmers and it contains the following sections:
- Same Origin Policy
- Signed Script Policy
- Using Expanded Privileges
- Writing the Script
- Signing Scripts
- Troubleshooting Signed Scripts
Same Origin Policy
The same origin policy prevents document or script loaded from one origin, from getting or setting properties from a of a document from a different origin.
Mozilla defines the origin as the substring of a URL that includes
protocol://host
where host
includes the optional :port
part. To illustrate, this table gives examples of origin comparisons to the URL
http://company.com/dir/page.html
.
URL | Outcome | Reason |
---|---|---|
http://company.com/dir2/other.html
|
Success | |
http://company.com/dir/inner/another.html
|
Success | |
http://www.company.com/dir/other.html
|
Failure | Different domain |
file://D|/myPage.htm
|
Failure | Different protocol |
http://company.com:80/dir/etc.html
|
Failure | Different port |
There is one exception to the same origin rule. A script can set the value of
document.domain
to a suffix of the current domain. If it does so, the
shorter domain is used for subsequent origin checks. For example, assume a script in
the document at http://www.company.com/dir/other.html
executes this
statement:
document.domain = "company.com";
After execution of that statement, the page would pass the origin check with
http://company.com/dir/page.html
.
However, using the same reasoning, company.com
could NOT set
document.domain
to othercompany.com
.
Signed Script Policy
Signing scripts involves generating a digital
signature and associating that signature with the script it signs. In
Communicator 4.x, this association was accomplished by adding the ARCHIVE="..."
attribute to a SCRIPT
tag to refer to the java
archive (JAR) containing the signature for the script. In
Mozilla, this association is handled differently. An entire HTML page and any scripts
it includes using a <script src="..."
tag are signed and placed in a
JAR file along with their associated
signature. By referring to the HTML page using the
jar:http://www.site.com/myjar.jar!/signed.html
syntax, the signature is
automatically associated with the script, and verified as part of the loading of the
page. Special HTML syntax to identify signed scripts (the ARCHIVE and ID attributes)
is unnecessary in Mozilla and is no longer recognized. The JavaScript security model
for signed scripts is based upon the Java security model for signed objects from
Communicator 4.x. By signing a script using a valid certificate issued from a certificate
authority (such as VeriSign) you certify
that you are the owner of the script and that the script was not modified before
reaching the end user. Because signed scripts offer this proof of identity, only
signed scripts can be granted extended privileges by the browser. Using this model
you can sign any JavaScript in an HTML page or referred to by the HTML page.
A signed script requests expanded privileges, gaining access to restricted information. You can use these expanded privileges to exercise fine-grained control over activities beyond those which are normally allowed to JavaScript.
All access-control decisions boil down to who is allowed to do what. In this model, a principal represents the "who," the target represents the "what" and the privilege associated with a principal represents the authorization (or denial of authorization) for code signed by a principal to access a specific privilege.
Once you have written the script, you sign it using Netscape's SignTool. SignTool associates a digital signature with HTML and JS files. That digital signature is owned by a particular principal (a real-world entity such as Netscape or John Smith). The digital signature and the files it signs are both placed in a Java Archive (JAR) file.
The associated principal allows the user to confirm the identity of the entity which signed the script. It also allows the user to ensure that the script hasn't been tampered with since it was signed. The user then can decide whether to grant privileges based on the validated identity of the certificate owner and integrity of the script.
You should always keep in mind that a user may deny the privileges requested by your script. You should write your scripts to react gracefully to such decisions. See the exception handling section.
This document assumes that you are already familiar with the basic principals of object signing, using the JavaScript API and creating digital signatures. The following documents provide information on these subjects:
Object Signing References
- Netscape Object Signing: Establishing Trust for Downloaded Software provides an overview of object signing. Be sure you understand this material before using signed scripts.
- Using SignTool describes the signing tool for creating signed JavaScript scripts.
- The JAR Format contains detailed information on the JAR format.
- JavaScript Guide contains information about using JavaScript.
- Mozilla JavaScript documentation explains the current JavaScript projects and has helpful links to other helpful pages.
Codebase Principals
JavaScript supports codebase principals. A codebase principal is a principal derived from the origin of the script rather than from verifying a digital signature of a certificate. Since codebase principals offer weaker security, they are disabled by default in Mozilla. Codebase principals do not offer as strong a proof of identity, thus end users are unable to make informed choices on whether to grant the script extended privileges.
To enable codebase principals, end users must add this line to the Mozilla prefs.js file:
user_pref("signed.applets.codebase_principal_support", true);
For deployment, your scripts should not rely on codebase principals being enabled. You might want to enable codebase principals when developing your scripts, but you should sign them before delivery.
Even when codebase principals are disabled, Mozilla keeps track of codebase principals to use in enforcement of the same origin security policy, described in "Same Origin Policy". Unsigned scripts have an associated set of principals that contains a single element, the codebase principal for the page containing the script. Signed scripts also have codebase principals in addition to the stronger certificate principals.
With codebase principals enabled, when the user accesses the script, a dialog displays similar to the one displayed with signed scripts. The difference is that this dialog asks the user to grant privileges based on the URL and doesn't provide author verification.
Scripts Signed by Different Principals
JavaScript differs from Java in several important ways that relate to security. Java signs classes and is able to protect internal methods of those classes through the public/private/protected mechanism. Marking a method as protected or private immediately protects it from an attacker. In addition, any class or method marked final in Java cannot be extended and so is protected from an attacker.
On the other hand, because JavaScript has no concept of public and private methods, there are no internal methods that could be protected by simply signing a class. In addition, all methods can be changed at runtime, so must be protected at runtime.
In JavaScript you can add new properties to existing objects, or replace existing properties (including methods) at runtime. You cannot do this in Java. So, once again, protection that is automatic in Java must be handled separately in JavaScript.
While the signed script security model for JavaScript is based on the object signing model for Java, these differences in the languages mean that when JavaScript scripts produced by different principals interact, it is much harder to protect the scripts. Because all of the JavaScript code on a single HTML page runs in the same process, different scripts on the same page can change each other's behavior. For example, a script might redefine a function defined by an earlier script on the same page.
To ensure security, the basic assumption of the JavaScript signed script security model is that mixed scripts on an HTML page operate as if they were all signed by the intersection of the principals that signed each script. This is very important in Mozilla. If you have a web page with signed and unsigned code, the entire page will be regarded as unsigned. In addition, only one signature should be assigned to each JAR file. Mozilla does not currently support multiple signatures.
Using Expanded Privileges
Mozilla uses the JavaScript privileges API.
In the simplest case, you add one line of code asking permission to enable a privilege which allows a script to access a target. For example:
netscape.security.PrivilegeManager.enablePrivilege("UniversalPreferencesRead")
or
netscape.security.PrivilegeManager.enablePrivilege("UniversalPreferencesWrite")
When the script calls this function, if the signature is valid or codebase principal are enabled, expanded privileges can be granted. If a user has not accessed this principal before, a dialog asks the user if he wants to accept the signed code. Unlike Communicator 4.x, Mozilla does not display a detailed Java grant dialog, rather a simple dialog asking if the principal can be trusted. The user can accept or deny and allow their choice to be remembered by the browser. As shown in the second example, two privileges may be asked for at once, so only one dialog appears.
Privileges are granted only in the scope of the requesting function. This scope includes any functions called by the requesting function. When the script leaves the requesting function, privileges no longer apply.
The following example demonstrates this by printing:
7: disabled 5: disabled 2: disabled 3: enabled 1: enabled 4: enabled 6: disabled 8: disabled
Function g requests expanded privileges, and only the commands and functions called after the request and within function g are granted privileges. It is good practice to enable privileges only when needed, then disable the privilege soon after the code is executed. This will help protect against potentially dangerous sections of code being run on the user's computer.
<script type="text/javascript"> function printEnabled(i) { if (history[0] == "") { document.write(i + ": disabled<br>"); } else { document.write(i + ": enabled<br>"); } } function f() { printEnabled(1); } function g() { printEnabled(2); netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); printEnabled(3); f(); printEnabled(4); } function h() { printEnabled(5); g(); printEnabled(6); } printEnabled(7); h(); printEnabled(8); </script>
Privileges
Privilege represents permissions to access a specific target. The following table lists JavaScript built in privilege and the targets associated with them.
Privileges | Targets |
---|---|
UniversalBrowserRead
|
Reading of sensitive browser data. This allows the script to pass the same origin check when reading from any document. |
UniversalBrowserWrite
|
Modification of sensitive browser data. This allows the script to pass the same origin check when writing to any document. |
UniversalXPConnect
|
Unrestricted access to browser APIs using XPConnect |
UniversalPreferencesRead
|
Read preferences using the navigator.preference method. |
UniversalPreferencesWrite
|
Set preferences using the navigator.preference method. |
UniversalFileRead
|
Access to file:// URLs. |
In addition, any DOM propertycan be associated with a privilege (user defined) as discussed in the "Configurable Security" section.
JavaScript Features Requiring Privileges
This section lists the JavaScript features that require expanded privileges and the target used to access each feature. Unsigned scripts cannot use any of these features, unless the end user has enabled codebase principals.
- Using an
about:
URL other thanabout:blank
requiresUniversalBrowserRead
. history
object: Getting the value of any property requiresUniversalBrowserRead
.-
navigator
object:- Getting the value of a preference using the
preference
method requiresUniversalPreferencesRead
. - Setting the value of a preference using the
preference
method requiresUniversalPreferencesWrite
.
- Getting the value of a preference using the
-
window
object: All of the following operations requireUniversalBrowserWrite
.- Adding or removing the directory bar (or personal bar), location bar, menu bar, scroll bar, status bar or (navigation) toolbar.
- Using the methods in the following table under the indicated circumstances
-
enableExternalCapture
To capture events in pages loaded from different servers. Follow this method with captureEvents
.close
To unconditionally close a browser window. moveBy
To move a window off screen. moveTo
To move a window off screen. open
- To create a window smaller than 100 x 100
pixels or larger than the screen can accommodate
by using
innerWidth
,innerHeight
,outerWidth
, andouterHeight
. - To place a window off screen by using
screenX
andscreenY
. - To create a window without a title bar by
using
titlebar
. - To use
alwaysRaised
,alwaysLowered
, orz-lock
for any setting.
resizeTo
To resize a window smaller than 100 x 100 pixels or larger than the screen can accommodate. resizeBy
To resize a window smaller than 100 x 100 pixels or larger than the screen can accommodate. - To create a window smaller than 100 x 100
pixels or larger than the screen can accommodate
by using
- Setting the properties in the following table under the indicated circumstances:
-
innerWidth
To set the inner width of a window to a size smaller than 100 x 100 or larger than the screen can accommodate. innerHeight
To set the inner height of a window to a size smaller than 100 x 100 or larger than the screen can accommodate.
Example
The following script includes a button, that, when clicked, displays an alert dialog containing part of the URL history of the browser. To work properly, the script must be signed.
<script type="text/javascript"> function getHistory(i) { //Attempt to access privileged information return history[i]; } function getImmediateHistory() { //Request privilege netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); return getHistory(1); } </script> ... <input type="button" onclick="alert(getImmediateHistory());" id="b">
Writing the Script
This section describes special considerations for writing signed scripts.
International Characters in Signed Scripts
When used in scripts, international characters can appear in string constants and in comments. JavaScript keywords and variables cannot include special international characters.
Scripts that include international characters cannot be signed because the process of transforming the characters to the local character set invalidates the signature. To work around this limitation:
- Escape the international characters ('0x\ea', and so on).
- Put the data containing the international characters in a hidden form element, and access the form element through the signed script.
- Separate signed and unsigned scripts into different layers, and use the international characters in the unsigned scripts.
- Remove comments that include international characters.
Hints for Writing Secure JavaScript
Check the Location of the Script
If you have signed scripts in pages you have posted to your site, it is possible to copy the JAR file from your site and post it on another site. As long as the signed scripts themselves are not altered, the scripts will continue to operate under your signature. (See "Debugging Invalid Signature Errors" for one exception to this rule.)
If you wish to prevent this, you can force your scripts to work only from your site.
<script type="text/javascript">
if (document.URL.match(/^http:\/\/www.company.com\//)) {
netscape.security.PrivilegeManager.enablePrivilege(...);
// Do your stuff
}
</script>
Then if the JAR file and script are copied to another site, they no longer work. If the person who copies the script alters it to bypass the check on the source of the script, the signature is invalidated.
Minimize the Trusted Code Base
In security parlance, the Trusted Code Base (TCB) is the set of code that has privileges to perform restricted actions. One way to improve security is reduce the size of the TCB, which then gives fewer points for attack or opportunities for mistakes.
For example, the following code, if executed in a signed script with the user's approval, opens a new window containing the history of the browser:
<script type="text/javascript"> netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserAccess"); var win = window.open(); for (var i=0; i < history.length; i++) { win.document.writeln(history[i] + "<br>"); } win.close(); </script>
The TCB in this instance is the entire script because privileges are acquired at the beginning and never reverted. You could reduce the TCB by rewriting the program as follows:
<script type="text/javascript"> var win = window.open(); netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserAccess"); for (var i=0; i < history.length; i++) { win.document.writeln(history[i] + "<br>"); } netscape.security.PrivilegeManager.revertPrivilege("UniversalBrowserAccess"); win.close(); </script>
With this change, the TCB becomes only the
loop containing the accesses to the history
property. You could avoid the
extra call to revert the privilege by introducing a function:
<script type="text/javascript"> function writeArray() { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserAccess"); for (var i=0; i < history.length; i++) { win.document.writeln(history[i] + "<br>"); } } var win = window.open(); writeArray(); win.close(); </script>
The privileges are automatically reverted when writeArray
returns, so you
don't have to do so explicitly.
Signing Scripts
During development of a script you'll eventually sign, you can use codebase principals for testing, as described in "Codebase Principals". Once you've finished modifying the script, you need to sign it. The major difference in signing scripts between 4.x and Mozilla is that in Mozilla, the entire page must be signed, as opposed to only the script running on the page. For any script to be granted expanded privileges, all scripts on or included by an HTML page must be signed.
You can sign JavaScript files (accessed with the src
attribute of the
script
tag), inline scripts, event handler scripts JavaScript entities and
javascript:
URLs.
Using SignTool
Use SignTool to sign scripts. SignTool is a program that signs scripts and HTML files, and packages them in a JAR file with the signature.
The signtool program extracts scripts from HTML files, signs them, and
places their digital signatures in the archive specified
in the command line. It also takes care of copying external JavaScript files loaded
by the src
attribute of the script
tag. The
script
tags in the HTML pages can specify more than one
JAR file; if so, signtool creates
as many JAR files as it needs.
For information on using this tool, see Using SignTool.
Here is an example of the syntax needed for signing scripts
% signtool -k"Cert Name" -Z"secure.jar" secure-files/ This command will create a JAR file (secure.jar) signed by "Cert Name". All the JavaScript and HTML files in the directory secure-files/ will be signed and stored in the JAR file. |
After Signing
Once you've signed a script, any time you change it you must resign it. For JavaScript files, this means you cannot change anything in the file. A change can be as simple as adding or removing white space in the script.
For testing, use SignTool to create a test certificate (see documentation). However, end users will not be able to use the test certificate, so remember to obtain a certificate from a certificate authority in order to serve a signed script on the web.
Accessing a Signed Page
New in Mozilla is the syntax needed to access signed scripts within JAR files. The syntax is as follows:
jar:http://www.domain.com/secure-scripts/secure.jar!/thepage.html
Scripts will only be treated as signed if the HTML page that contains them is using a URL of this form. Changes to a signed script's byte stream invalidate the script's signature. This includes moving the HTML page between platforms that have different representations of text. For example, moving an HTML page from a Windows server to a UNIX server changes the byte stream and invalidates the signature. (This doesn't affect viewing pages from multiple platforms.) To avoid this, you can move the page in binary mode. Note that doing so changes the appearance of the page in your text editor but not in the browser.
Troubleshooting Signed Scripts
Exception Handling
Exception handling is highly recommended when using signed scripts. It allows you to deal gracefully with errors or the user choosing to deny a privilege.
For example:
<script type="text/javascript">
function getPriv() {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalPreferencesRead");
} catch (err) {
document.write("Sorry, you can not enjoy this site because of " +err+ ".");
return false;
}
document.write("Thanks, privileges accepted");
//... Do stuff
return true;
}
</script>
If the user denies the privilege, an exception will be thrown and the program will
immediately execute the code in the catch
section. If there is no catch
clause, the script will end abroptly, and an error will be shown in the JavaScript
console.
Errors on the JavaScript Console
Be sure to check the JavaScript console for errors if your signed scripts do not function as expected. Remember, if you are using exception handling, you will not see the errors in the JS Console. You may see errors such as the following:
# Signature Verification Error: the signature on # securitycheck.jar is invalid because # the archive did not contain a valid PKCS7 signature.
The path value printed for signed JavaScript is either the value of the
ID
attribute or the src
attribute of the tag that supplied the
script.
Debugging Invalid Signature Errors
Invalid signature errors occur if the script has changed from when it was signed. The most common cause of this problem is that the scripts have been moved from one platform to another with a text transfer rather than a binary transfer. Because line separator characters can differ from platform to platform, the hash could change from when the script was originally signed.
"User did not grant privilege" Exception or Unsigned Script Dialog
Depending on whether or not you have enabled codebase principals, you see different behavior if a script attempts to enable privileges when it isn't signed or when its principals have been downgraded due to mixing of signed and unsigned scripts.
If you have not enabled codebase principals and an unsigned script attempts to enable privileges, it gets an exception from Java that the "Enable privilege not granted". If you did enable codebase principals, you will see a security dialog asking for permissions for the unsigned code.
Communicator 4.x links
- For more tips on writing your scripts, see Danny Goodman's View Source article, Applying Signed Scripts.