Skip to content

Project Status Update #935

@mscdex

Description

@mscdex

For the past few months I've been working on a large rewrite of ssh2. The
original reasons for this were performance-related and design-related.

The performance reason came about when I recalled performing some profiling about
a year or so ago and found that the node crypto stuff was dominating the time
spent by the process. Unfortunately I didn't try to reproduce these results
before I started working on reworking the crypto code in ssh2 because I found
out afterwards I was unable to reproduce the profiling results, no matter the
configuration or situation. In hindsight, this makes sense because in most cases
the network should always be the bottleneck.

The design reason came about because I was wanting to add support for
chacha20-poly1305 and doing so was very difficult partly due to how packets were
being parsed and partly because of some confusion around how OpenSSL's
chacha20-poly1305 implementation works (which incidentally is not compatible
with OpenSSH's chacha20-poly1305 implementation).

Just recently I've merged all of my work into the master branch. I would not
recommend using this in production yet, however I am looking for people to give
the code a try as I know between all of the changes and the feature additions
the code coverage is surely to have gone down (this is something that will need
to be remedied in due time). Additionally the documentation may not be 100% yet
either.

The end goal is that once I feel comfortable (especially after sufficient
testing has been performed), I will be releasing this new code as v1.0.0 and
v0.8.x will remain in a separate branch for historical purposes (I do not have
plans to maintain it going forward).

Here's a rundown of some various things I jotted down as I was working on this:

(Potentially) Breaking changes

  • node v10.16.0 is the new minimum required/supported node version. This allows us to
    both use newer code features present in V8 and to remove various polyfills for
    older node versions. There are also some other (crypto-related IIRC?) changes
    that led to this specific version being the new minimum.

  • No more internal backpressure (no more 'continue' event, etc.). Reasoning:

    • Node never standardized the mechanism that made it possible

    • Very small number of inquiries about the mechanism since its inception
      seemed to indicate few people paid attention to it anyway

    • It complicated the codebase

    • Along with streams in general, it was the source of various compatibility
      issues with node core over the years

    • Since the replacement of the "parser stream" (ssh2-streams) with a
      non-stream-based parser, this would make handling backpressure even
      trickier to implement

  • SFTP_STATUS_CODE and SFTP_OPEN_MODE are now moved to
    the utils.sftp exported namespace as STATUS_CODE and OPEN_MODE
    respectively

  • Client: hostVerifier() now passed a Buffer containing the raw host key
    when no (valid) hostHash is set

  • Client: hostVerifier() will now be called every time a handshake occurs.
    This is because the server can potentially change host keys later on
    during a rekey.

  • Client: an error will now be emitted when the connection is severed before
    the handshake begins

  • Server: algorithms.serverHostKey is now mostly ignored, in general the
    order of the host key algorithms now comes directly from the order of the
    server host keys that are passed in. However, for RSA keys, the order of
    algorithms.serverHostKey is used to determine the order of the various
    hashing algorithms available (ssh-rsa for SHA1, rsa-sha2-256 for SHA256,
    and rsa-sha2-512 for SHA512). If ssh-rsa is not in the list, it is given
    the lowest priority amongst the other possible hashing algorithms.

  • Server: The sigAlgo property of publickey and hostbased authentication
    contexts has been removed. It is recommended to instead use the simple verify()
    method of keys returned by the parseKey() utility for verifying signatures instead
    of using node's crypto API directly to perform the same task.

  • Server: 'x11' event's info.cookie is now a Buffer instead of hex string

New features

  • Optional binding for improved crypto performance and resource usage
    (in-place encrypt/decrypt also helps to reduce memory consumption and GC)

    • node v10.19.0 on i7-3770k

      • 1KB payload
        chacha20-poly1305 encrypt (Native -- Poly1305 WASM) (1024) x 66,964 ops/sec ±1.35% (81 runs sampled)
        chacha20-poly1305 encrypt (Binding) (1024) x 501,103 ops/sec ±0.35% (94 runs sampled)

        aes128-gcm encrypt (Native) (1024) x 132,410 ops/sec ±1.32% (85 runs sampled)
        aes128-gcm encrypt (Binding) (1024) x 1,024,079 ops/sec ±0.22% (89 runs sampled)

        aes128-ctr/hmac-sha2-256 encrypt (Native) (1024) x 101,521 ops/sec ±2.44% (88 runs sampled)
        aes128-ctr/hmac-sha2-256 encrypt (Binding) (1024) x 255,214 ops/sec ±1.69% (93 runs sampled)

      • 32KB payload (approx. max payload size for single packet)
        chacha20-poly1305 encrypt (Native -- Poly1305 WASM) (32768) x 12,792 ops/sec ±1.78% (86 runs sampled)
        chacha20-poly1305 encrypt (Binding) (32768) x 32,126 ops/sec ±0.20% (96 runs sampled)

        aes128-gcm encrypt (Native) (32768) x 31,189 ops/sec ±0.78% (91 runs sampled)
        aes128-gcm encrypt (Binding) (32768) x 44,914 ops/sec ±0.40% (97 runs sampled)

        aes128-ctr/hmac-sha2-256 encrypt (Native) (32768) x 9,559 ops/sec ±1.72% (88 runs sampled)
        aes128-ctr/hmac-sha2-256 encrypt (Binding) (32768) x 10,453 ops/sec ±0.77% (95 runs sampled)

    • node v14.2.0 on Odroid-C4 (ARM Cortex-A55)

      • 1KB payload
        chacha20-poly1305 encrypt (Native -- Poly1305 WASM) (1024) x 9,767 ops/sec ±7.19% (63 runs sampled)
        chacha20-poly1305 encrypt (Binding) (1024) x 108,616 ops/sec ±2.18% (81 runs sampled)

        aes128-gcm encrypt (Native) (1024) x 22,200 ops/sec ±9.19% (67 runs sampled)
        aes128-gcm encrypt (Binding) (1024) x 302,560 ops/sec ±1.58% (75 runs sampled)

        aes128-ctr/hmac-sha2-256 encrypt (Native) (1024) x 26,812 ops/sec ±11.08% (65 runs sampled)
        aes128-ctr/hmac-sha2-256 encrypt (Binding) (1024) x 202,023 ops/sec ±3.15% (78 runs sampled)

      • 32KB payload
        chacha20-poly1305 encrypt (Native -- Poly1305 WASM) (32768) x 2,536 ops/sec ±2.29% (77 runs sampled)
        chacha20-poly1305 encrypt (Binding) (32768) x 8,407 ops/sec ±0.05% (81 runs sampled)

        aes128-gcm encrypt (Native) (32768) x 10,395 ops/sec ±3.59% (76 runs sampled)
        aes128-gcm encrypt (Binding) (32768) x 19,194 ops/sec ±0.04% (81 runs sampled)

        aes128-ctr/hmac-sha2-256 encrypt (Native) (32768) x 8,660 ops/sec ±3.74% (73 runs sampled)
        aes128-ctr/hmac-sha2-256 encrypt (Binding) (32768) x 13,534 ops/sec ±0.11% (81 runs sampled)

    • node v12.15.0 on Odroid-C2 (ARM Cortex-A53)

      • 1KB payload
        chacha20-poly1305 encrypt (Native -- Poly1305 WASM) (1024) x 10,398 ops/sec ±8.30% (62 runs sampled)
        chacha20-poly1305 encrypt (Binding) (1024) x 50,428 ops/sec ±0.70% (74 runs sampled)

        aes128-gcm encrypt (Native) (1024) x 13,855 ops/sec ±5.09% (68 runs sampled)
        aes128-gcm encrypt (Binding) (1024) x 26,351 ops/sec ±0.73% (75 runs sampled)

        aes128-ctr/hmac-sha2-256 encrypt (Native) (1024) x 13,712 ops/sec ±5.43% (72 runs sampled)
        aes128-ctr/hmac-sha2-256 encrypt (Binding) (1024) x 23,959 ops/sec ±0.50% (77 runs sampled)

      • 32KB payload
        chacha20-poly1305 encrypt (Native -- Poly1305 WASM) (32768) x 1,776 ops/sec ±1.85% (75 runs sampled)
        chacha20-poly1305 encrypt (Binding) (32768) x 2,327 ops/sec ±0.12% (80 runs sampled)

        aes128-gcm encrypt (Native) (32768) x 881 ops/sec ±0.90% (76 runs sampled)
        aes128-gcm encrypt (Binding) (32768) x 928 ops/sec ±0.04% (79 runs sampled)

        aes128-ctr/hmac-sha2-256 encrypt (Native) (32768) x 825 ops/sec ±0.72% (77 runs sampled)
        aes128-ctr/hmac-sha2-256 encrypt (Binding) (32768) x 871 ops/sec ±0.22% (79 runs sampled)

  • Support for rsa-sha2-256 and rsa-sha2-512 server host key algorithms

  • Support for -etm HMACs

  • Support for chacha20-poly1305@openssh.com

    • Two implementations:

      • chacha20 (binding) + poly1305 (binding)

      • chacha20 (from node core) + poly1305 WASM

    • Priority in default cipher list varies depending on CPU and binding availability. This was done
      after some benchmarking on various platforms

  • Negotiated handshake algorithms during each key exchange now accessible

  • More flexible algorithm configuration

    • Append/prepend to and remove from default lists instead of being forced
      to set exact lists

    • Use RegExps for even greater flexibility

Misc. changes

  • Server: if there is no 'sftp' session event handler but a 'subsystem'
    session event handler exists, it will be called as a fallback

  • Protocol implementation moved back into ssh2 repo, but still lives
    separate from rest of the client/server code. Reasoning:

    • Needed to keep both repos in sync all of the time

    • People would get confused about where to lodge an issue/PR

    • ssh2 was more or less the only user of ssh2-streams

    • Note: An sftp stream interface is not yet available, so if you need this
      (e.g. implementing a standalone SFTP subsystem for an OpenSSH server)
      you will want to keep with ssh2-streams for the time being

  • Code simplified and organized better
    (e.g. key exchange handling/logic in a separate file/module/class, ditto
    for encryption/decryption, etc.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions