Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize state_out and state_in #333

Open
Mikescops opened this issue Feb 15, 2024 · 4 comments
Open

Serialize state_out and state_in #333

Mikescops opened this issue Feb 15, 2024 · 4 comments

Comments

@Mikescops
Copy link

Hello,

I'm wondering if there is a way to serialize the state_in and the state_out from crypto_secretstream_xchacha20poly1305_init_push and crypto_secretstream_xchacha20poly1305_init_pull in order to store them in a persistent memory.

My first use case is a client / server exchanges where the server is stateless and cannot keep in RAM the states.

My second use case is a client / server exchanges where the server is a set of multiple machines behind a load balancer where the client has no guarantee to hit the same machine in a row.

Thanks!

@jedisct1
Copy link
Owner

Yes, that should totally be doable. The state is just the address of a 52 byte array, that can be safely moved to different hosts.

@Mikescops
Copy link
Author

@jedisct1 thanks, where that change should happen? in the parent libsodium library?

@jedisct1
Copy link
Owner

No, just in the JavaScript code. I think it can be done even without changing libsodium-wrappers.

@Mikescops
Copy link
Author

Mikescops commented Feb 16, 2024

First, I simply printed the the state_in and and state_out value. They seems to be numbers, which is not exactly what the @types/libsodium-wrappers is expecting.

What's weird too is that even if I rebuild the project and run multiple times i always get the same numbers.

state_in =  102240 
state_out = 102144 

So I tried to read through the wrapper code to understand what's going on.

I took a look at where the crypto_secretstream_xchacha20poly1305_init_pull is used:

      var h = new u(52).address;
            if (0 == (0 | a._crypto_secretstream_xchacha20poly1305_init_pull(h, n, c))) {
                var p = h;
                return g(_),
                p
            }
            b(_, "invalid usage")

In this code:

  • h is a new address for the state_in
  • n is the other party header
  • c is a shared key

and then where crypto_secretstream_xchacha20poly1305_init_push is used:

            var s = new u(52).address,
            c = new u(0 | a._crypto_secretstream_xchacha20poly1305_headerbytes()),
            o = c.address;
            if (t.push(o), 0 == (0 | a._crypto_secretstream_xchacha20poly1305_init_push(s, o, _))) {
                var h = {
                    state: s,
                    header: y(c, r)
                };
                return g(t),
                h
            }
            b(t, "invalid usage")

In this code:

  • s is a new address for the state_out
  • o is a new address for the secret stream header
  • _ is a shared key

Now taking a look at the function u() that seems to allocate some memory.

        function u(e) {
            this.length = e,
            this.address = v(e)
        }
        function d(e) {
            var r = v(e.length);
            return a.HEAPU8.set(e, r),
            r
        }
        function v(e) {
            var r = a._malloc(e);
            if (0 === r) throw {
                message: "_malloc() failed",
                length: e
            };
            return r
        }

Sounds like it's a binding to a malloc (probably to the C code?).

What I get from this point is that the state and the header are constructed the same way with the u() function, and from the output of the function we get the address in a Uint8Array which is easily convertible to any other format.

The call to y(c, r) seems to do the trick, so let's look at the code:

function y(e, r) {
            var a = r || t;
            if (!i(a)) throw new Error(a   " output format is not available");
            if (e instanceof u) {
                if ("uint8array" === a) return e.to_Uint8Array();
                if ("text" === a) return s(e.to_Uint8Array());
                if ("hex" === a) return c(e.to_Uint8Array());
                if ("base64" === a) return p(e.to_Uint8Array(), o.URLSAFE_NO_PADDING);
                throw new Error('What is output format "'   a   '"?')
            }
            if ("object" == typeof e) {
                for (var _ = Object.keys(e), n = {},
                h = 0; h < _.length; h  ) n[_[h]] = y(e[_[h]], a);
                return n
            }
            if ("string" == typeof e) return e;
            throw new TypeError("Cannot format output")
        }

So the .to_Uint8Array() method from the prototype is converting this address to the needed Uint8Array, which look like this in the code:

 return u.prototype.to_Uint8Array = function() {
            var e = new Uint8Array(this.length);
            return e.set(a.HEAPU8.subarray(this.address, this.address   this.length)),
            e
        },

In the end the issue relies on the fact that for the state we return only the address var s = new u(52).address while for the header we convert the entire buffer to a to_Uint8Array().

Two options:

  • either the function outputs the buffer content and not just the address
  • a method is available to read the memory from the address that is returned, similar to : a.HEAPU8.subarray(this.address, this.address this.length)

What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants