MatrixAI / Polykey

Polykey Core Library
https://polykey.com
GNU General Public License v3.0
31 stars 4 forks source link

Cross-Platform Standardisation #422

Open CMCDragonkai opened 2 years ago

CMCDragonkai commented 2 years ago

Specification

We want PK to be truly cross-platform. But PK is very complex, involving many components are not truly cross-platform. These components include:

Over the development process of PK, we've found many roadblocks to achieve a truly cross-platform PK system. This issue tracks all of these areas, and the progress towards more cross-platform deployment necessitating multiple new solutions in multiple areas.

This issue is likely to be a moving target, since cross platform development solutions keeps evolving.

Additional context

Tasks

  1. ...
  2. ...
  3. ...
CMCDragonkai commented 2 years ago

HTTP2 servers are compatible with HTTP1.1 with TLS. The way this works is through ALPN. This is done at the TLS-level. See: https://superuser.com/questions/1659248/how-does-browser-know-which-version-of-http-it-should-use-when-sending-a-request

In Nodejs the HTTP2 module is capable of accepting HTTP1.1 with TLS connections: https://nodejs.org/api/http2.html#alpn-negotiation.

If an HTTP3 server is available, an HTTP2 client would have to be "upgraded" to the HTTP3 protocol. It is not possible for an HTTP2 client to directly contact HTTP3, because it is a UDP port.

That means if we are using tunneling http2 which multiplex our jsonrpc into utp-native. Our server side should still receive requests outside of utp-native as just normal http1.1+TLS or http2 on TCP. In fact this is exactly how our GRPC server does right now, it accepts connections on TCP and through the utp-native proxy tunnel.

CMCDragonkai commented 2 years ago

Moving this to main Polykey repository now.

CMCDragonkai commented 2 years ago

While working on #466, I discovered:

As you can see there are some libraries for webcrypto in react native, however they are not fully supported, and some rely on webview bridges. It seems there are 3 possibilities:

  1. Webview bridge - probably the slowest
  2. Native modules Java and Objective C
  3. C++ using JSI - I like this method the best if we are going to use crypto in particular
  4. WASM - this would still need native system methods for getting useful entropy
CMCDragonkai commented 2 years ago

In our latest PR #446 we are making use of webcrypto API. However the actual implementation is backed by @peculiar/webcrypto which is a polyfill. In particular it has a different implementation of the Ed25519 algorithms. This is because neither browser webcrypto nor nodejs have a common implementation of Ed25519 and X25519. Nodejs has its own experimental implementation that is not compatible: https://github.com/PeculiarVentures/webcrypto/issues/55

The polyfill is designed for nodejs, it builds up a webcrypto API based on node's crypto API. This is therefore not going to be useful in the case of react native.

There's an additional library: https://github.com/PeculiarVentures/webcrypto-liner that polyfills browser implementations in the same vein. Again not entirely useful for mobile OS, since we don't work directly in a browser. And electron would use the node polyfill anyway.

The current benchmarks show some significant improvement:

Current performance notes:

  1. Symmetric operations is 10x improvement
  2. Random bytes generation is 100x improvement
  3. Generating root key is 3563x improvement (deterministic is 100x - 400x faster due to pbkdf2 is still slow)
  4. Asymmetric encryption didn't change much, but the fact that you can now encrypt arbitrary data sizes is significant.
  5. Asymmetric decryption is is 68x faster
  6. Asymmetric signing is 300x faster
  7. Asymmetric verification didn't change much

At the very least we have a situation where we expect there to be a globalThis.crypto object that facilitates webcrypto API, in TS, this is the Crypto type. This allows all of our libraries that support webcrypto to leverage that instead of using other crypto APIs, this includes all of the noble libraries and JOSE libraries.

Now as I said before, we still have to deal with mobile OS, and we deal with that in still the 4 ways:

  1. Webview bridge - probably the slowest
  2. Native modules Java and Objective C
  3. C++ using JSI - I like this method the best if we are going to use crypto in particular
  4. WASM - this would still need native system methods for getting useful entropy

I want to provide some possible avenues of approach for each:

  1. https://github.com/webview-crypto/react-native-webview-crypto
  2. Native modules
  3. C++ using JSI
  4. WASM

The webview looks unmaintained but it could be done.

What is of interest is the libsodium library. It is claimed that libsodium is meant for cross-platform usage. Look at all the available bindings: https://doc.libsodium.org/bindings_for_other_languages. The fact that there are multiple implementations and the fact that they have been made WASM capable, as well as native node implementations of sodium should meant that libsodium might be exactly what we've been looking for.

Because our libraries still expect a webcrypto API, we may just need to reimplement the webcrypto API using an underlying libsodium library which then means:

  1. Top-level libraries: JOSE, @noble, @scure... etc
  2. Webcrypto interface monkey patched as globalThis.crypto
  3. Libsodium implementation of webcrypto - provide both native variant from nodejs, as well as WASM variant or native variant for react native

Given the current benchmark numbers, we now have a way of comparing if any particular choice will maintain decent performance of our required crypto primitives.

CMCDragonkai commented 2 years ago

I'd be curious exactly how fast/slow the WASM variants are compared to native code. We're using webcrypto now which I believe ultimately uses node's openssl code which is native, but how much are we losing by using WASM. This allows us to know if it's feasible to use WASM, especially since on ios there's no JIT.

CMCDragonkai commented 2 years ago

In order to use any of the WASM based libraries, we have to have top level await. This is because the wasm libraries all require asynchronous initialisation.

Without a top level await, we would end up having to export promises.

Now top level await is only available as part of the greater migration towards EcmaScript modules over CJS modules.

The transition to ESM is going to be tricky, but this https://www.typescriptlang.org/docs/handbook/esm-node.html provides more information on how to do this.

We have an issue tracking this in https://github.com/MatrixAI/TypeScript-Demo-Lib/issues/32.

Since we are still inter-operating with old CJS modules, most likely this means we are using NodeNext for both moduleResolution and module.

However our ts-node doesn't yet support ESM yet, so until we also change that over if they finally acquire support for this. See: https://github.com/TypeStrong/ts-node/issues/1007

At any case, since WASM is unlikely to be ready for mobile deployment, we're likely to not bother with the WASM variants of libsodium anyway.

CMCDragonkai commented 2 years ago

I've moved to using libsodium in https://github.com/MatrixAI/Polykey/pull/446#issuecomment-1271185189. However there are 2 places that still require webcrypto. Future work should work on these systems to remove the reliance on webcrypto.

In this case, all crypto related functionality will be on top of libsodium.

CMCDragonkai commented 1 year ago

A few other things:

  1. js-quic still failing on windows build
  2. js-db needs to be turned into using optional dependencies
  3. js-mdns now has some code that is only compiled for linux

Some things to factor out:

  1. js-ws - this needs to be factored out of Polykey, as interim we can use just the ws node package, but later convert it to a runtime-less library like quiche

526 is really important cause lots of things are using TLS, and centralising the TLS would be essential:

If we want to centralise to particular TLS implementation, we might even need a separate http related implementation... or at least redirect its usage of TLS by plugging our own TLS to mock over the TLS used by http/http2 module?

One could potentially override nodejs with boringssl alternative, then force the usage of that in js-quic. Will need to dive deeper into how nodejs is compiled to achieve this.