chromeos-ssh-smartcard-hack icon indicating copy to clipboard operation
chromeos-ssh-smartcard-hack copied to clipboard

Some code to duct-tape an SSH agent to a Chrome extension that implements the chrome.certificateProvider API.

ChromeOS SSH SmartCard Hack

This repository contains some code to duct-tape an SSH agent to a Chrome extension that implements the chrome.certificateProvider API. This can make it possible to use Smart Cards with the Secure Shell app!

This is forked from, and heavily based on, the MacGyver extension developed by the good folks at Stripe.

Background

Recently, Chrome OS introduced full Smart Card support, along with the chrome.certificateProvider API that allows Smart Card middleware extensions to provide certificates (and the ability to sign data) to ChromeOS for TLS client authentication.

Separately, the Secure Shell extension for Chrome (which is an OpenSSH compiled for NaCl) supports using an external extension as a stand-in for an SSH agent.

The interface offered by chrome.certificateProvider extensions provides all the necessary cryptographic capabilities to implement a full-fledged SSH Agent extension that uses keypairs stored on smart cards. However, at this time, there is no clean way for another extension to access this functionality. Our solution is, instead, to inject a modified version of the MacGyver code into a chrome.certificateProvider extension. It turns out this is surprisingly straightforward!

Usage

After installing a properly-hacked extension, you can pass --ssh-agent=extensionid in the "relay options" field (not the "SSH Arguments"!) of the Secure Shell app.

Performing the Hack

The SSH Agent code is written in Go, and compiled to JavaScript using GopherJS. Using Go lets us take advantage of packages like x/crypto, which already has an SSH agent implementation.

You can compile the extension by running the following:

  • go get -u github.com/gopherjs/gopherjs
  • cd go && gopherjs build

Then, take an existing chrome.certificateProvider extension, unpack it if necessary, and do the following:

  • Copy the 'go' directory and all its contents into the unpacked extension directory
  • Edit manifest.json
    • Add "go/go.js" as a background script.

      That is, if manifest.json initially contains:

      "app": {
          "background": {
             "persistent": false,
             "scripts": [ "background.js" ]
          }
      },
      

      Then modify it to contain:

      "app": {
          "background": {
             "persistent": false,
             "scripts": [ "background.js", "go/go.js" ]
          }
      },
      
    • Allow messaging from the Secure Shell app.

      That is, add:

      "externally_connectable": {
          "ids": [
              "pnhechapfaindjhompbnflcldabbghjo",
              "okddffdblfhhnmhodogpojmfkjmhinfp"
              ]
      },
      

      as a top-level key. (You may want to use the original manifest.json from MacGyver as a reference.)

Permissions

In order to communicate with smart card hardware, middleware extensions (typically, those that implement the chrome.certificateProvider interface) must use Google's Smart Card Connector app. Unfortunately, this app has a highly restrictive model for API permissions, in that it only accepts communications from whitelisted extensions. When you modify an extension as described above, you will typically end up with a new extension ID that is not on this whitelist.

If you are deploying this extension onto enterprise-managed chromebooks, you can attach policy to the Smart Card Connector app to override this whitelist, as Google documents in their discussion of API permissions.

Otherwise, with some JS hackery, it's possible to force-whitelist a Chrome extension. To do this:

  1. Navigate to chrome://extensions/
  2. Ensure "Developer Mode" is checked
  3. Beneath the "Smart Card Connector" app, click the link to inspect the 'background page'
  4. In the JS console, type: $jscomp.scope.permissionsChecker.userPromptingChecker_.storeUserSelection_('YOUR_EXTENSION_ID', true)

(Obviously, there's a risk this technique might break with a future update to the Smart Card Connector app!)

Chrome SSH Agent Protocol

The Secure Shell extension for Chrome has supported relaying the SSH agent protocol to another extension since November 2014. The protocol is fairly straightforward, but undocumented.

The SSH agent protocol is based on a simple length-prefixed framing protocol. Each message is prefixed with a 4-byte network-encoded length. Messages are sent over a UNIX socket.

By contrast, the Secure Shell agent protocol uses Chrome cross-extension messaging, connecting to the agent extension with chrome.runtime.connect. Each frame of the SSH agent protocol is assembled, stripped of its length prefix, and sent as an array of numbers (not, say, an ArrayBuffer) in the "data" field of an object via postMessage.

Here's an example message, representing the SSH2_AGENTC_REQUEST_IDENTITIES request (to list keys):

{
  "type": "[email protected]",
  "data": [11]
}

SSH agents are expected to respond in the same format.

macgyver.AgentPort

Because x/crypto's SSH agent implementation expects an io.ReadWriter that implements the standard (length-prefixed) protocol, MacGyver implements a wrapper around a chrome.runtime.Port that between Secure Shell's protocol and the native protocol (stripping or adding the length prefix and JSON object wrapper as necessary).

Contributors

MacGyver extension:

  • Evan Broder
  • Dan Benamy

CertificateProvider modifications:

  • Adam Goodman