.--~~~~~~~~~~~~~------. /--===============------\ | |```````````````| | | | | | | | >_< | | | | | | | |_______________| | | ::::| '=======================' //-"-"-"-"-"-"-"-"-"-"-\\ //_"_"_"_"_"_"_"_"_"_"_"_\\ [-------------------------] \_________________________/ Secure Shell Developer Guide
Secure Shell is a Chrome App (currently a “v1.5” app, soon to become a “v2” or Platform App) 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.
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.sh
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.
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):
# In the ssh_client/ directory. $ cp -a output/hterm/plugin/ ../nassh/
# In the nassh/ directory. $ wget https://commondatastorage.googleapis.com/chromeos-localmirror/secureshell/releases/0.8.39.tar.xz $ tar --strip-components=1 -xf 0.8.39.tar.xz hterm/plugin
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/
.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.
Now that your checkout is ready, you can load it into Chrome.
chrome://extensions
page.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.
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 manifest_v2.json is not used currently. Some day we might finish the migration and replace manifest_v1.5.json with it so we only have one app manifest. Today it is not often tested.
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.
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.
terminalPrivate
). (1) (2) (3)fileSystemProvider
and file_system_provider_capabilities
). (1) To double check what & where things are whitelisted, search the Chromium code base for our extension ids:
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
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.
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.
While most of the UI code for crosh lives here (e.g. HTML/CSS/JS), the backend code and manifest lives in Chrome.
chrome.terminalPrivate
JavaScript API.chrome.terminalPrivate
JavaScript API.api/terminal/
code.See the libapps hacking document for details.
Keep in mind that the NaCl ssh_client code does not live here.
The vast majority of the code here lives under js/.
*.concat.js
: Compiled JS output of other projects we use.nassh
object setup and glue code to Chrome runtime.nassh.App
code.nassh.CommandInstance
launching code.nassh.GoogleRelay
code for proxying connections.nassh.Stream
streams.*_tests.js
: Each module has a corresponding set of unittests. The filename follows the convention of adding _tests
. e.g. nassh_tests.js
contains the tests for nassh.js
.There are a few specialized modules that are not relevant to the core Secure Shell logic.
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.
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 JSON string 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 name | Description | Arguments |
---|---|---|
startSession | Start a new ssh connection! | (object session ) |
onOpenFile | Open a new file. | (int fd , bool success , bool is_atty ) |
onOpenSocket | Open a new socket. | (int fd , bool success , bool is_atty ) |
onRead | Send new data to the plugin. | (int fd , base64 data ) |
onWriteAcknowledge | Tell plugin we've read data. | (int fd , int count ) |
onClose | Close an existing fd. | (int fd ) |
onReadReady | Notify plugin data is available. | (int fd , bool result ) |
onResize | Notify terminal size changes. | (int width , int height ) |
onExitAcknowledge | Used to quit the plugin. | () |
The session object currently has these members:
username
: Username for accessing the remote system.host
: Hostname for accessing the remote system.port
: Port number for accessing the remote system.terminalWidth
: Initial width of the terminal window.terminalHeight
: Initial height of the terminal window.useJsSocket
: Whether to use JS for network traffic.environment
: A key/value object of environment variables.arguments
: Extra command line options for ssh.writeWindow
: Size of the write window.authAgentAppID
: Extension id to use as the ssh-agent.subsystem
: Which subsystem to launch.Here is the API that the NaCl ssh_client code uses to communicate with the JS layers.
At the lowest level, we pass a JSON string 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 name | Description | Arguments |
---|---|---|
openFile | Plugin wants to open a file. | (int fd , str path , int mode ) |
openSocket | Plugin wants to open a socket. | (int fd , str host , int port ) |
read | Plugin wants to read data. | (int fd , int count ) |
write | Plugin wants to write data. | (int fd , base64 data ) |
close | Plugin wants to close an fd. | (int fd ) |
isReadReady | Plugin wants to know read status. | (int fd ) |
exit | The plugin is exiting. | (int code ) |
printLog | Send a string to console.log . | (str str ) |
On Chrome OS, it is possible to mount remote paths via SFTP and the Files app. We currently support version 3 of the protocol.
The extension provides an external API to allow other apps/extensions to invoke SFTP mount using Chrome messaging. The nassh background page adds a listener for chrome.runtime.onMessageExternal
which can be invoked by other apps / extensions by calling chrome.runtime.sendMessage
with the following fields in message:
Field name | Type | Description |
---|---|---|
command | !string | Command to run. Currently only mount supported |
Field name | Type | Description |
---|---|---|
knownHosts | !string | File contents of known_hosts to be used for connection. e.g. output from ssh-keyscan <ssh-server> |
identityFile | !string | File contents of private key identity_file (e.g. contents of id_rsa file -----BEGIN RSA PRIVATE KEY----- ... ) |
username | !string | Username for connection |
hostname | !string | Hostname or IP address for connection |
port | number | (optional) Port, default is 22 |
fileSystemId | !string | ID used for Chrome OS mounted filesystem |
displayName | !string | Display name in Chrome OS Files.app for mounted filesystem |
Here's a random list of documents which would be useful to people.