0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-20 03:52:17 +02:00

SVR3: Node bridge

This commit is contained in:
moiseev-signal 2024-02-23 14:50:53 -08:00 committed by GitHub
parent a4da946705
commit 536ec242c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 429 additions and 24 deletions

2
node/Native.d.ts vendored
View File

@ -430,6 +430,8 @@ export function SignedPreKeyRecord_GetSignature(obj: Wrapper<SignedPreKeyRecord>
export function SignedPreKeyRecord_GetTimestamp(obj: Wrapper<SignedPreKeyRecord>): Timestamp;
export function SignedPreKeyRecord_New(id: number, timestamp: Timestamp, pubKey: Wrapper<PublicKey>, privKey: Wrapper<PrivateKey>, signature: Buffer): SignedPreKeyRecord;
export function SignedPreKeyRecord_Serialize(obj: Wrapper<SignedPreKeyRecord>): Buffer;
export function Svr3Backup(asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, secret: Buffer, password: string, maxTries: number, username: string, enclavePassword: string, opTimeoutMs: number): Promise<Buffer>;
export function Svr3Restore(asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, password: string, shareSet: Buffer, username: string, enclavePassword: string, opTimeoutMs: number): Promise<Buffer>;
export function TESTING_CdsiLookupErrorConvert(): void;
export function TESTING_CdsiLookupResponseConvert(asyncRuntime: Wrapper<TokioAsyncContext>): Promise<LookupResponse>;
export function TESTING_ErrorOnBorrowAsync(_input: null): Promise<void>;

View File

@ -7,7 +7,7 @@ import * as Native from '../Native';
import * as uuid from 'uuid';
enum ServiceIdKind {
export enum ServiceIdKind {
Aci = 0,
Pni,
}

View File

@ -40,6 +40,10 @@ export enum ErrorCode {
InvalidUsernameLinkEncryptedData,
RateLimitedError,
SvrDataMissing,
SvrRequestFailed,
SvrRestoreFailed,
}
export class LibSignalErrorBase extends Error {
@ -195,6 +199,18 @@ export type RateLimitedError = LibSignalErrorBase & {
readonly retryAfterSecs: number;
};
export type SvrDataMissingError = LibSignalErrorBase & {
code: ErrorCode.SvrDataMissing;
};
export type SvrRequestFailedError = LibSignalErrorCommon & {
code: ErrorCode.SvrRequestFailed;
};
export type SvrRestoreFailedError = LibSignalErrorCommon & {
code: ErrorCode.SvrRestoreFailed;
};
export type LibSignalError =
| GenericError
| DuplicatedMessageError
@ -221,4 +237,7 @@ export type LibSignalError =
| InvalidUsernameLinkEncryptedData
| IoError
| InvalidMediaInputError
| SvrDataMissingError
| SvrRestoreFailedError
| SvrRequestFailedError
| UnsupportedMediaInputError;

View File

@ -32,6 +32,11 @@
*/
import * as Native from '../Native';
import {
IoError,
InvalidMediaInputError,
UnsupportedMediaInputError,
} from './Errors';
import { InputStream } from './io';
export class SanitizedMetadata {
@ -49,7 +54,7 @@ export class SanitizedMetadata {
/**
* Get the sanitized metadata, if any.
* @returns The sanitized metadata, or {@code null} if it didn't need to be sanitized.
* @returns The sanitized metadata, or `null` if it didn't need to be sanitized.
*/
getMetadata(): Buffer | null {
const metadata = Native.SanitizedMetadata_GetMetadata(this);
@ -80,11 +85,11 @@ export class SanitizedMetadata {
* Sanitize an MP4 input.
*
* @param input An MP4 format input stream.
* @param length The exact length of the input stream.
* @param len The exact length of the input stream.
* @returns The sanitized metadata.
* @throws IoError If an IO error on the input occurs.
* @throws InvalidMediaInputError If the input could not be parsed because it was invalid.
* @throws UnsupportedMediaInputError If the input could not be parsed because it's unsupported in some way.
* @throws {IoError} If an IO error on the input occurs.
* @throws {InvalidMediaInputError} If the input could not be parsed because it was invalid.
* @throws {UnsupportedMediaInputError} If the input could not be parsed because it's unsupported in some way.
*/
export async function sanitize(
input: InputStream,

View File

@ -13,15 +13,19 @@
*/
import * as Native from '../Native';
import {
IoError,
InvalidMediaInputError,
UnsupportedMediaInputError,
} from './Errors';
/**
* Sanitize a WebP input.
*
* @param input A WebP format input stream.
* @param length The exact length of the input stream.
* @throws IoError If an IO error on the input occurs.
* @throws InvalidMediaInputError If the input could not be parsed because it was invalid.
* @throws UnsupportedMediaInputError If the input could not be parsed because it's unsupported in some way.
* @throws {IoError} If an IO error on the input occurs.
* @throws {InvalidMediaInputError} If the input could not be parsed because it was invalid.
* @throws {UnsupportedMediaInputError} If the input could not be parsed because it's unsupported in some way.
*/
export function sanitize(input: Buffer): void {
Native.WebpSanitizer_Sanitize(input);

View File

@ -1379,7 +1379,7 @@ export class SealedSenderDecryptionResult {
}
}
interface CiphertextMessageConvertible {
export interface CiphertextMessageConvertible {
asCiphertextMessage(): CiphertextMessage;
}
@ -1602,7 +1602,7 @@ export function sealedSenderEncrypt(
return Native.SealedSender_Encrypt(address, content, identityStore);
}
type SealedSenderMultiRecipientEncryptOptions = {
export type SealedSenderMultiRecipientEncryptOptions = {
content: UnidentifiedSenderMessageContent;
recipients: ProtocolAddress[];
excludedRecipients?: ServiceId[];

View File

@ -6,6 +6,12 @@
import type { ReadonlyDeep } from 'type-fest';
import * as Native from '../Native';
import { Aci } from './Address';
import {
IoError,
SvrDataMissingError,
SvrRestoreFailedError,
SvrRequestFailedError,
} from './Errors';
// This must match the libsignal-bridge Rust enum of the same name.
export enum Environment {
@ -13,7 +19,7 @@ export enum Environment {
Production = 1,
}
export type CDSAuthType = {
export type ServiceAuth = {
username: string;
password: string;
};
@ -44,13 +50,19 @@ export class Net {
private readonly _asyncContext: Native.TokioAsyncContext;
private readonly _connectionManager: Native.ConnectionManager;
/**
* Instance of the {@link Svr3Client} to access SVR3.
*/
svr3: Svr3Client;
constructor(env: Environment) {
this._asyncContext = Native.TokioAsyncContext_new();
this._connectionManager = Native.ConnectionManager_new(env);
this.svr3 = new Svr3ClientImpl(this._asyncContext, this._connectionManager);
}
async cdsiLookup(
{ username, password }: Readonly<CDSAuthType>,
{ username, password }: Readonly<ServiceAuth>,
{
e164s,
acisAndAccessKeys,
@ -91,3 +103,154 @@ export class Net {
);
}
}
/**
* This interface provides functionality for communicating with SVR3
*
* Its instance can be obtained from an {@link Net#svr3} property
* of the {@link Net} class.
*
* Example usage:
*
* @example
* ```ts
* import { Environment, Net } from '../net';
* // Obtain an instance
* const SVR3 = new Net(Environment.Staging).svr3;
* // Instantiate ServiceAuth with the username and password obtained from the Chat Server.
* const auth = { username: USERNAME, password: ENCLAVE_PASSWORD };
* // Store a value in SVR3. Here 10 is the number of permitted restore attempts.
* const shareSet = await SVR3.backup(SECRET_TO_BE_STORED, PASSWORD, 10, auth, TIMEOUT);
* const restoredSecret = await SVR3.restore( PASSWORD, shareSet, auth, TIMEOUT);
* ```
*/
export interface Svr3Client {
/**
* Backup a secret to SVR3.
*
* Error messages are expected to be log-safe and not contain any sensitive
* data.
*
* @param what - The secret to be stored. Must be 32 bytes long.
* @param password - User-provide password that will be used to derive the
* encryption key for the secret.
* @param maxTries - Number of times the secret will be allowed to be guessed.
* Each call to {@link Svr3Client#restore} that has reached the server will
* decrement the counter. Must be positive.
* @param auth - An instance of {@link ServiceAuth} containing the username
* and password obtained from the Chat Server. The password is an OTP which is
* generally good for about 15 minutes, therefore it can be reused for the
* subsequent calls to either backup or restore that are not too far apart in
* time.
* @param opTimeoutMs - The maximum wall time libsignal is allowed to spend
* communicating with SVR3 service.
* @returns A `Promise` which--when awaited--will return a byte array with a
* serialized masked share set. It is supposed to be an opaque blob for the
* clients and therefore no assumptions should be made about its contents.
* This byte array should be stored by the clients and used to restore the
* secret along with the password. Please note that masked share set does not
* have to be treated as secret.
*
* The returned `Promise` can also fail due to the network issues (including the
* timeout), problems establishing the Noise connection to the enclaves, or
* invalid arguments' values. {@link IoError} errors can, in general, be
* retried, although there is already a retry-with-backoff mechanism inside
* libsignal used to connect to the SVR3 servers. Other exceptions are caused
* by the bad input or data missing on the server. They are therefore
* non-actionable and are guaranteed to be thrown again when retried.
*/
backup(
what: Buffer,
password: string,
maxTries: number,
auth: Readonly<ServiceAuth>,
opTimeoutMs: number
): Promise<Buffer>;
/**
* Restore a secret from SVR3.
*
* Error messages are expected to be log-safe and not contain any sensitive
* data.
*
* @param password - User-provide password that will be used to derive the
* decryption key for the secret.
* @param shareSet - a serialized masked share set returned by a call to
* {@link Svr3Client#backup}.
* @param auth - An instance of {@link ServiceAuth} containing the username
* and password obtained from the Chat Server. The password is an OTP which is
* generally good for about 15 minutes, therefore it can be reused for the
* subsequent calls to either backup or restore that are not too far apart in
* time.
* @param opTimeoutMs - The maximum wall time libsignal is allowed to spend
* communicating with SVR3 service.
* @returns A `Promise` which--when awaited--will return a byte array with the
* restored secret.
*
* The returned `Promise` can also fail due to the network issues (including the
* timeout), problems establishing the Noise connection to the enclaves, or
* invalid arguments' values. {@link IoError} errors can, in general, be
* retried, although there is already a retry-with-backoff mechanism inside
* libsignal used to connect to the SVR3 servers. Other exceptions are caused
* by the bad input or data missing on the server. They are therefore
* non-actionable and are guaranteed to be thrown again when retried.
*
* - {@link SvrDataMissingError} is returned when the maximum restore attempts
* number has been exceeded or if the value has never been backed up.
* - {@link SvrRestoreFailedError} is returned when the combination of the
* password and masked share set does not result in successful restoration
* of the secret.
* - {@link SvrRequestFailedError} is returned when the de-serialization of a
* masked share set fails, or when the server requests fail for reasons
* other than "maximum attempts exceeded".
*/
restore(
password: string,
shareSet: Buffer,
auth: Readonly<ServiceAuth>,
opTimeoutMs: number
): Promise<Buffer>;
}
class Svr3ClientImpl implements Svr3Client {
constructor(
private readonly _asyncContext: Native.TokioAsyncContext,
private readonly _connectionManager: Native.ConnectionManager
) {}
async backup(
what: Buffer,
password: string,
maxTries: number,
auth: Readonly<ServiceAuth>,
opTimeoutMs: number
): Promise<Buffer> {
return Native.Svr3Backup(
{ _nativeHandle: this._asyncContext },
{ _nativeHandle: this._connectionManager },
what,
password,
maxTries,
auth.username,
auth.password,
opTimeoutMs
);
}
async restore(
password: string,
shareSet: Buffer,
auth: Readonly<ServiceAuth>,
opTimeoutMs: number
): Promise<Buffer> {
return Native.Svr3Restore(
{ _nativeHandle: this._asyncContext },
{ _nativeHandle: this._connectionManager },
password,
shareSet,
auth.username,
auth.password,
opTimeoutMs
);
}
}

View File

@ -3,11 +3,16 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import { config, expect } from 'chai';
import { config, expect, use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as util from './util';
import { Aci, Pni } from '../Address';
import * as Native from '../../Native';
import { ErrorCode, LibSignalErrorBase } from '../Errors';
import { Environment, Net, ServiceAuth } from '../net';
import { randomBytes } from 'crypto';
use(chaiAsPromised);
util.initLogger();
config.truncateThreshold = 0;
@ -50,3 +55,118 @@ describe('cdsi lookup', () => {
});
});
});
describe('SVR3', () => {
const TIMEOUT = 5000;
const USERNAME = randomBytes(16).toString('hex');
const SVR3 = new Net(Environment.Staging).svr3;
function make_auth(): Readonly<ServiceAuth> {
const otp = Native.CreateOTPFromBase64(
USERNAME,
// Empty string is a valid base64 encoding
process.env.ENCLAVE_SECRET || ''
);
return { username: USERNAME, password: otp };
}
describe('Backup', () => {
// It is OK to reuse the auth in "input validation" tests.
const AUTH = make_auth();
it('maxTries must be positive', () => {
const secret = randomBytes(32);
return expect(SVR3.backup(secret, 'password', 0, AUTH, TIMEOUT)).to
.eventually.be.rejected;
});
it('Secret must be 32 bytes', () => {
const secret = randomBytes(42);
return expect(SVR3.backup(secret, 'password', 1, AUTH, TIMEOUT)).to
.eventually.be.rejected;
});
});
describe('Restore', () => {
it('Empty share set', () => {
const auth = make_auth();
const shareSet = Buffer.alloc(0);
return expect(
SVR3.restore('password', shareSet, auth, TIMEOUT)
).to.eventually.be.rejectedWith(LibSignalErrorBase);
});
it('Share set bad format', () => {
const auth = make_auth();
const shareSet = Buffer.from([42]);
return expect(
SVR3.restore('password', shareSet, auth, TIMEOUT)
).to.eventually.be.rejectedWith(LibSignalErrorBase);
});
});
// Integration tests require access to the staging environment and make real
// network calls and as such require the secret (and lacking the secret will
// not be run).
describe('Integration tests', function (this: Mocha.Suite) {
before(() => {
if (!process.env.ENCLAVE_SECRET) {
this.ctx.skip();
}
});
it('Backup and restore work in staging', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const shareSet = await SVR3.backup(secret, 'password', 10, auth, TIMEOUT);
const restoredSecret = await SVR3.restore(
'password',
shareSet,
auth,
TIMEOUT
);
expect(restoredSecret).to.eql(secret);
}).timeout(10000);
it('Restore with wrong password', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const shareSet = await SVR3.backup(secret, 'password', 10, auth, TIMEOUT);
return expect(SVR3.restore('wrong password', shareSet, auth, TIMEOUT))
.to.eventually.be.rejectedWith(LibSignalErrorBase)
.and.have.property('code', ErrorCode.SvrRestoreFailed);
}).timeout(10000);
it('Restore with corrupted share set', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const shareSet = await SVR3.backup(secret, 'password', 10, auth, TIMEOUT);
// The first byte is the serialization format version, changing that
// _will_ fail (checked in the other test). Changing the actual share set
// value makes a more interesting test case.
shareSet[1] ^= 0xff;
return expect(
SVR3.restore('password', shareSet, auth, TIMEOUT)
).to.eventually.be.rejectedWith(LibSignalErrorBase);
}).timeout(10000);
it('Exceed maxTries', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const shareSet = await SVR3.backup(secret, 'password', 1, auth, TIMEOUT);
await SVR3.restore('password', shareSet, auth, TIMEOUT);
return expect(SVR3.restore('password', shareSet, auth, TIMEOUT))
.to.eventually.be.rejectedWith(LibSignalErrorBase)
.and.have.property('code', ErrorCode.SvrDataMissing);
});
it('Timeout', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const SHORT_TIMEOUT = 100;
return expect(SVR3.backup(secret, 'password', 10, auth, SHORT_TIMEOUT))
.to.eventually.be.rejectedWith(LibSignalErrorBase)
.and.have.property('code', ErrorCode.IoError);
});
});
});

View File

@ -333,6 +333,11 @@ ansi-regex@^6.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
ansi-sequence-parser@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf"
integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
@ -1666,6 +1671,11 @@ json5@^1.0.2:
dependencies:
minimist "^1.2.0"
jsonc-parser@^3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a"
integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@ -1720,6 +1730,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
lunr@^2.3.9:
version "2.3.9"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
make-fetch-happen@^13.0.0:
version "13.0.0"
resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz#705d6f6cbd7faecb8eac2432f551e49475bfedf0"
@ -1737,6 +1752,11 @@ make-fetch-happen@^13.0.0:
promise-retry "^2.0.1"
ssri "^10.0.0"
marked@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3"
integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@ -1764,7 +1784,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimatch@^9.0.1:
minimatch@^9.0.1, minimatch@^9.0.3:
version "9.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
@ -2308,6 +2328,16 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shiki@^0.14.7:
version "0.14.7"
resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.7.tgz#c3c9e1853e9737845f1d2ef81b31bcfb07056d4e"
integrity sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==
dependencies:
ansi-sequence-parser "^1.1.0"
jsonc-parser "^3.2.0"
vscode-oniguruma "^1.7.0"
vscode-textmate "^8.0.0"
side-channel@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
@ -2610,6 +2640,16 @@ typed-array-length@^1.0.4:
for-each "^0.3.3"
is-typed-array "^1.1.9"
typedoc@^0.25.8:
version "0.25.8"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.25.8.tgz#7d0e1bf12d23bf1c459fd4893c82cb855911ff12"
integrity sha512-mh8oLW66nwmeB9uTa0Bdcjfis+48bAjSH3uqdzSuSawfduROQLlXw//WSNZLYDdhmMVB7YcYZicq6e8T0d271A==
dependencies:
lunr "^2.3.9"
marked "^4.3.0"
minimatch "^9.0.3"
shiki "^0.14.7"
typescript@4.9.3:
version "4.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db"
@ -2666,6 +2706,16 @@ uuid@^8.3.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
vscode-oniguruma@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b"
integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==
vscode-textmate@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d"
integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"

View File

@ -33,6 +33,7 @@ def translate_to_ts(typ):
"String": "string",
"&str": "string",
"Vec<u8>": "Buffer",
"Box<[u8]>": "Buffer",
"ServiceId": "Buffer",
"Aci": "Buffer",
"Pni": "Buffer",

View File

@ -27,7 +27,7 @@ use crate::support::*;
use crate::*;
cfg_if! {
if #[cfg(feature = "jni")] {
if #[cfg(any(feature = "jni", feature = "node"))] {
use futures_util::future::TryFutureExt as _;
use rand::rngs::OsRng;
use std::num::NonZeroU32;
@ -88,7 +88,7 @@ impl Environment {
pub struct ConnectionManager {
cdsi: EndpointConnection<Cdsi, MultiRouteConnectionManager, TcpSslTransportConnector>,
#[cfg(feature = "jni")]
#[cfg(any(feature = "jni", feature = "node"))]
svr3: (
EndpointConnection<Sgx, MultiRouteConnectionManager, TcpSslTransportConnector>,
EndpointConnection<Nitro, MultiRouteConnectionManager, TcpSslTransportConnector>,
@ -100,7 +100,7 @@ impl ConnectionManager {
fn new(environment: Environment) -> Self {
Self {
cdsi: Self::endpoint_connection(environment.env().cdsi),
#[cfg(feature = "jni")]
#[cfg(any(feature = "jni", feature = "node"))]
svr3: (
Self::endpoint_connection(environment.env().svr3.sgx()),
Self::endpoint_connection(environment.env().svr3.nitro()),
@ -250,7 +250,7 @@ fn CreateOTPFromBase64(username: String, secret: String) -> String {
Auth::otp(&username, &secret, std::time::SystemTime::now())
}
#[bridge_io(TokioAsyncContext, ffi = false, node = false)]
#[bridge_io(TokioAsyncContext, ffi = false)]
async fn Svr3Backup(
connection_manager: &ConnectionManager,
secret: Box<[u8]>,
@ -279,7 +279,7 @@ async fn Svr3Backup(
Ok(share_set.serialize().expect("can serialize the share set"))
}
#[bridge_io(TokioAsyncContext, ffi = false, node = false)]
#[bridge_io(TokioAsyncContext, ffi = false)]
async fn Svr3Restore(
connection_manager: &ConnectionManager,
password: String,
@ -301,7 +301,7 @@ async fn Svr3Restore(
Ok(restored_secret.to_vec())
}
#[cfg(feature = "jni")]
#[cfg(any(feature = "jni", feature = "node"))]
async fn svr3_connect<'a>(
connection_manager: &ConnectionManager,
username: String,

View File

@ -351,6 +351,14 @@ impl SimpleArgTypeInfo for bool {
}
}
impl SimpleArgTypeInfo for Box<[u8]> {
type ArgType = JsBuffer;
fn convert_from(cx: &mut FunctionContext, foreign: Handle<Self::ArgType>) -> NeonResult<Self> {
Ok(foreign.as_slice(cx).to_vec().into())
}
}
/// Converts `null` to `None`, passing through all other values.
impl<'storage, 'context: 'storage, T> ArgTypeInfo<'storage, 'context> for Option<T>
where

View File

@ -4,6 +4,7 @@
//
use std::fmt;
use libsignal_net::svr3::Error as Svr3Error;
use paste::paste;
use signal_media::sanitize::mp4::{Error as Mp4Error, ParseError as Mp4ParseError};
use signal_media::sanitize::webp::{Error as WebpError, ParseError as WebpParseError};
@ -131,9 +132,12 @@ pub trait SignalNodeError: Sized + fmt::Display {
}
}
const RATE_LIMITED_ERROR: &str = "RateLimitedError";
const IO_ERROR: &str = "IoError";
const INVALID_MEDIA_INPUT: &str = "InvalidMediaInput";
const IO_ERROR: &str = "IoError";
const RATE_LIMITED_ERROR: &str = "RateLimitedError";
const SVR3_DATA_MISSING: &str = "SvrDataMissing";
const SVR3_REQUEST_FAILED: &str = "SvrRequestFailed";
const SVR3_RESTORE_FAILED: &str = "SvrRestoreFailed";
const UNSUPPORTED_MEDIA_INPUT: &str = "UnsupportedMediaInput";
impl SignalNodeError for neon::result::Throw {
@ -431,6 +435,35 @@ impl SignalNodeError for libsignal_net::cdsi::LookupError {
}
}
impl SignalNodeError for libsignal_net::svr3::Error {
fn throw<'a>(
self,
cx: &mut impl Context<'a>,
module: Handle<'a, JsObject>,
operation_name: &str,
) -> JsResult<'a, JsValue> {
let name = match self {
Svr3Error::Net(_) => Some(IO_ERROR),
Svr3Error::AttestationError(inner) => {
return inner.throw(cx, module, operation_name);
}
Svr3Error::RequestFailed(_) => Some(SVR3_REQUEST_FAILED),
Svr3Error::RestoreFailed => Some(SVR3_RESTORE_FAILED),
Svr3Error::DataMissing => Some(SVR3_DATA_MISSING),
Svr3Error::Protocol(_) => None,
};
let message = self.to_string();
match new_js_error(cx, module, name, &message, operation_name, None) {
Some(error) => cx.throw(error),
None => {
// Make sure we still throw something.
cx.throw_error(message)
}
}
}
}
/// Represents an error returned by a callback.
#[derive(Debug)]
struct CallbackError {