.--~~~~~~~~~~~~~------.
                           /--===============------\
                           | |```````````````|     |
                           | |               |     |
                           | |      >_<      |     |
                           | |               |     |
                           | |_______________|     |
                           |                   ::::|
                           '======================='
                           //-"-"-"-"-"-"-"-"-"-"-\\
                          //_"_"_"_"_"_"_"_"_"_"_"_\\
                          [-------------------------]
                          \_________________________/


                          Secure Shell Developer Guide

Introduction

Secure Shell is a Chrome extension that combines hterm with a NaCl build of OpenSSH to provide a PuTTY-like app for Chrome users.

See /HACK.md for general information about working with the source control setup.

Building the dependencies

The Secure Shell app depends on some library code from libapps/libdot/ and the hterm terminal emulator from in libapps/hterm/. To build these external dependencies, run...

nassh$ ./bin/mkdeps

This will create the nassh/js/nassh_deps.concat.js file containing all of the necessary libdot and hterm source. If you find yourself changing a lot of libdot or hterm code and testing those changes in Secure Shell you can run this script with the “--forever” (aka -f) option. When run in this manner it will automatically re-create nassh_deps.concat.js file whenever one of the source files is changed.

The NaCl plugin dependency

Secure Shell depends on a NaCl (Native Client) plugin to function. This plugin is a port of OpenSSH. You'll have to find or create a version of this plugin, and copy it into libapps/nassh/plugin/.

Your options are (pick one):

  1. Build it yourself from [ssh_client]. This can take some time, but once it's finished:
# In the ssh_client/ directory.
$ cp -a output/hterm/plugin/ ../nassh/
  1. Grab an existing release. For example:
# In the nassh/ directory.
$ wget https://commondatastorage.googleapis.com/chromeos-localmirror/secureshell/releases/0.27.tar.xz
$ tar -xvf 0.27.tar.xz plugin/
  1. Copy the plugin/ directory from the latest version of Secure Shell. If you have Secure Shell installed, the plugin can be found in your profile directory, under Default/Extensions/pnhechapfaindjhompbnflcldabbghjo/<version>/plugin/.

Dev-Test cycle

Loading Unpacked Extensions

Loading directly from the checked out nassh directory is the normal way of testing. It will use the dev extension id to avoid conflicts with the stable extension id, although it will still conflict if you have the dev version installed from the Chrome Web Store (CWS).

You will need to manually select the variant you want to work on. The extension is defined by the manifest_ext.json file while the app is defined by the manifest_v1.5.json file. Simply symlink it to manifest.json:

nassh$ ln -s manifest_ext.json manifest.json

For details on the different manifests and modes, see the next section (and the FAQ). You probably want to start with the extension version if you're not going to be hacking on Chrome OS features.

The extension id is controlled by the key field in the manifest.json. See the manifest key docs for more details.

Adding To Chrome

Now that your checkout is ready, you can load it into Chrome.

  1. Navigate to the chrome://extensions page.
  2. Turn on Developer Mode (look for the toggle in the upper right or bottom right of the page depending on Chrome version).
  3. Click Load Unpacked Extension and navigate to the nassh/ directory.

If you‘re not running on Chrome OS device, and loading the app, you might see warnings right away about certain permissions (see the whitelisted sections below). You can ignore those. It’s unfortunate they show up with the same level/color as legitmate errors.

* 'file_system_provider_capabilities' is only allowed for extensions and packaged apps, but this is a legacy packaged app.
* 'terminalPrivate' is not allowed for specified platform.
* 'fileSystemProvider' is not allowed for specified platform.

Manifests

There are three manifest files in here currently. The manifest_v1.5.json is used to build the Secure Shell App and has been what we've used for the longest time, but is largely for Chrome OS only now. The manifest_ext.json is used to build the Secure Shell Extension which works on all platforms (but lacks any Chrome OS specific features).

The “v1.5” and “v2” app formats should not be confused with the “v1” and “v2” manifest formats. Secure Shell uses the legacy/deprecated “v1.5” app style to launch itself rather than the “v2” style. It means that, in many ways, the “v1.5” app behaves more like an extension (e.g. it shares cookies with your main browser instance and can run inside a tab) rather than an app (e.g. it doesn't get access to many newer chrome.app.* APIs).

See the FAQ for more details on the differences between the extension & app.

If you‘re updating these files, you’ll sometimes also need to update the manifest for [crosh] which lives in the Chromium tree.

Whitelisted Permissions

Using the dev extension id is necessary in order to access some APIs that are whitelisted only for Secure Shell. If you don't need these features, you can get by with using a different id (and delete the settings from the manifest_v1.5.json for the app to avoid warnings at runtime). These settings are already removed from the manifest_ext.json for the extension.

  • Access to [crosh] under Chrome OS (terminalPrivate). (1) (2) (3)
  • Access to raw sockets under NaCl. This allows connecting directly to SSH servers (e.g. port 22). (1)
    Note: Making connections over https using relay servers will still work though. See the FAQ for more details.
  • Access to [chrome.sockets] APIs. This allows connecting directly to SSH servers (e.g. port 22). We're allowing this pending [Native Sockets] support in the web platform itself.
  • Access to chrome.crashReportPrivate APIs. This allows users to opt-in to providing crash-reports. Not required to be able to use Secure Shell of course.
  • Access to chrome.metricsPrivate APIs. This allows users to opt-in to metrics/UMA collection about basic features. Not required to be able to use Secure Shell of course.
  • SFTP backend for Chrome OS (fileSystemProvider and file_system_provider_capabilities). (1)
    Note: Normal extensions/apps can use these features, but since Secure Shell is still a “legacy packaged app”, we had to whitelist access.

To double check what & where things are whitelisted, search the Chromium code base for our extension ids:

NameIDhash
Stable Appcs/pnhechapfaindjhompbnflcldabbghjocs/0EA6B717932AD64C469C1CCB6911457733295907
Dev Appcs/okddffdblfhhnmhodogpojmfkjmhinfpcs/58B0C2968C335964D5433E89CA4D86628A0E3D4B
Stable Extensioncs/iodihamcpbpeioajjeobimgagajmlibdcs/3BC1ED0B3E6EFDC7BD4D3D1D75D44B52DEE0A226
Dev Extensioncs/algkcnfjnajfhgimadimbjhmpaeohhlncs/38C361D4A0726CE45D3572D65071B6BDB3092371
Croshcs/nkoccljplnhpfnfiajclkommnmllphnlcs/505FEAE9DD5B27637DCF72045ECA2D5D7F66D2FD

The hashes are the SHA1's of the (lower case) extension id.

$ ext_id_hash() { printf "$1" | tr '[:upper:]' '[:lower:]' | sha1sum | tr '[:lower:]' '[:upper:]'; }
$ ext_id_hash pnhechapfaindjhompbnflcldabbghjo
0EA6B717932AD64C469C1CCB6911457733295907

Partner Extensions

There are a few extensions we talk to for various services at runtime which need to whitelist our extension ids (for source verification). If you don't need any of these services, then you can ignore it.

Stable Extension

If you try to load an unpacked extension using the stable extension id, you might run into problems if your administrator installs it via enterprise policy. If you see the error below, you won't be able to bypass it. Just use the dev extension id instead.

Secure Shell (extension ID "pnhechapfaindjhompbnflcldabbghjo") is blocked by the administrator.

Crosh

While most of the UI code for [crosh] lives here (e.g. HTML/CSS/JS), the backend code and manifest lives in Chrome.

Coding Style

See the libapps hacking document for details.

Source Layout

Keep in mind that the NaCl [ssh_client] code does not live here.

The vast majority of the code here lives under js/.

JavaScript Source Layout

There are a few specialized modules that are not relevant to the core Secure Shell logic.

NaCl/JS Life cycle

When the extension is launched (e.g. a new connection is opened), the background page is automatically created. This is used to monitor global state like extension updates and coordinate SFTP mounts. The logic lives in nassh_background.js and takes care of creating a new instance of nassh.App which it saves in the background page‘s app variable. If you aren’t looking at the SFTP logic, you can probably ignore this code.

When the extension is run, a new nassh.html window is shown. If no connection info is provided via the URL, then an iframe is created to show nassh_connect_dialog.html. Here the user manages their saved list of connections and picks the one they want to connect to. This logic is in nassh_connect_dialog.js. Once the user makes a selection (either connecting or mounting), a message is sent to nassh_command_instance.js. There the connection dialog is closed, the NaCl plugin is loaded, and the streams are connected to hterm.

Exit Codes

Since the ssh program uses positive exit statuses, we tend to use -1 for internal exit states in the JS code. It doesn't matter too much as the exit values are purely for showing the user.

JS->NaCl API

Here is the API that the JS code uses to communicate with the NaCl [ssh_client] module.

The nassh.CommandInstance.prototype.sendToPlugin_ function in nassh_command_instance.js is used to package up and make all the calls. Helper functions are also provided in that file to avoid a JS API to callers.

At the lowest level, we pass a dictionary to the plugin. It has two fields, both of which must be specified (even if arguments is just []).

  • name: The function we want to call (as a string).
  • arguments: An array of arguments to the function.

The name field can be any one of:

Function nameDescriptionArguments
startSessionStart a new ssh connection!(object session)
onOpenFileOpen a new file.(int fd, bool success, bool is_atty)
onOpenSocketOpen a new socket.(int fd, bool success, bool is_atty)
onReadSend new data to the plugin.(int fd, ArrayBuffer data)
onWriteAcknowledgeTell plugin we've read data.(int fd, number count)
onCloseClose an existing fd.(int fd)
onReadReadyNotify plugin data is available.(int fd, bool result)
onResizeNotify terminal size changes.(int width, int height)
onExitAcknowledgeUsed to quit the plugin.()

The session object currently has these members:

  • str username: Username for accessing the remote system.
  • str host: Hostname for accessing the remote system.
  • int port: Port number for accessing the remote system.
  • int terminalWidth: Initial width of the terminal window.
  • int terminalHeight: Initial height of the terminal window.
  • bool useJsSocket: Whether to use JS for network traffic.
  • object environment: A key/value object of environment variables.
  • array arguments: Extra command line options for ssh.
  • int writeWindow: Size of the write window.
  • str authAgentAppID: Extension id to use as the ssh-agent.
  • str subsystem: Which subsystem to launch.

The onWriteAcknowledge count field tracks the total byte count sent for the connection, not the count from the most recent write request. It supports up to Number.MAX_SAFE_INTEGER bytes.

NaCl->JS API

Here is the API that the NaCl [ssh_client] code uses to communicate with the JS layers.

At the lowest level, we pass a dictionary to the JS code. It has two fields, both of which must be specified (even if arguments is just []).

  • name: The function we want to call (as a string).
  • arguments: An array of arguments to the function.

The name field can be any one of:

Function nameDescriptionArguments
openFilePlugin wants to open a file.(int fd, str path, int mode)
openSocketPlugin wants to open a socket.(int fd, str host, int port)
readPlugin wants to read data.(int fd, int count)
writePlugin wants to write data.(int fd, ArrayBuffer data)
closePlugin wants to close an fd.(int fd)
exitThe plugin is exiting.(int code)
printLogSend a string to console.log.(str str)

SFTP

On Chrome OS, it is possible to mount remote paths via SFTP and the Files app. We currently support [version 3][SFTPv3] of the protocol. We don't support newer standards because the most popular implementation is [OpenSSH]'s which only supports SFTPv3, and for the majority of Secure Shell users, they only interact with [OpenSSH].

We could support newer versions, but they wouldn‘t be well tested, and not a lot of people would even use it, and it’s not like we'd see any performance improvements (as our operations tend to be basic open/read/write/close).

Extensions

We support a few optional extensions to improve behavior or performance.

SSH_FXP_SYMLINK arguments

The [SFTPv3] protocol says the argument order should be linkpath then the targetpath, but OpenSSH made a mistake and reversed the order. They can't change the order without breaking existing clients or servers, so they document it and leave it as-is.

We follow the [OpenSSH SFTP Protocol] here, as do many other clients.

[SFTPv6] noted this desync between implementations and the specification and replaced the SSH_FXP_SYMLINK packet type with a new SSH_FXP_LINK.

fsync@openssh.com (v1)

The [OpenSSH SFTP Protocol] defines a fsync@openssh.com extension which takes an open handle and instructs the remote server to use the fsync() syscall.

We use this extension when the user has requested syncing after writes.

hardlink@openssh.com (v1)

The [OpenSSH SFTP Protocol] defines a hardlink@openssh.com extension with the same API as SSH_FXP_SYMLINK.

We use this extension when the user has requested hardlinks explicitly.

posix-rename@openssh.com (v1)

The [OpenSSH SFTP Protocol] defines a posix-rename@openssh.com extension with the same API as SSH_FXP_RENAME, but uses the simple rename(2) semantics on the server. Otherwise, the default rename operation can be a bit buggy/racy.

We use this extension when available, or fallback to SSH_FXP_RENAME if not.

statvfs@openssh.com (v2)

The [OpenSSH SFTP Protocol] defines a statvfs@openssh.com extension which returns the struct statvfs data from statvfs() syscall.

We use this extension when the user has requested filesystem statistics.

copy-data

The copy-data extension is great for speeding up remote copies as it avoids having to download data from one file and uploading to a different one.

References

Here's a random list of documents which would be useful to people.

[Corp Relay: relay-protocol.md#corp-relay [crosh]: chromeos-crosh.md [gnubbyd]: https://chrome.google.com/webstore/detail/beknehfpfkghjoafdifaflglpjkojoco [NaCl]: https://developer.chrome.com/native-client [Native Sockets]: https://crbug.com/909927 [OpenSSH]: https://www.openssh.com/ [OpenSSH SFTP Protocol]: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL [SFTPv3]: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 [SFTPv6]: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13 [ssh_client]: ../../ssh_client/ [wash]: ../../wash/