@zkp2p/reclaim-witness-sdk
Version:
<div> <div> <img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/Attestor-Core.png" /> </div> </div>
280 lines • 21.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tls_1 = require("@reclaimprotocol/tls");
const client_1 = require("../client");
const providers_1 = require("../providers");
const server_1 = require("../server");
const describe_with_server_1 = require("../tests/describe-with-server");
const mocks_1 = require("../tests/mocks");
const utils_1 = require("../tests/utils");
const utils_2 = require("../utils");
const TLS_VERSIONS = [
'TLS1_3',
'TLS1_2',
];
const OPRF_CIPHER_SUITES = [
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_256_GCM_SHA384',
'TLS_AES_128_GCM_SHA256',
];
jest.setTimeout(90000);
jest.mock('@reclaimprotocol/tls/lib/utils/parse-certificate', () => {
const actual = jest.requireActual('@reclaimprotocol/tls/lib/utils/parse-certificate');
return {
__esModule: true,
...actual,
verifyCertificateChain: jest.fn().mockImplementation()
};
});
(0, describe_with_server_1.describeWithServer)('Claim Creation', opts => {
const zkEngine = 'gnark';
let client;
let claimUrl;
beforeEach(() => {
client = opts.client;
claimUrl = `https://localhost:${opts.mockhttpsServerPort}/me`;
// we need to disable certificate verification
// for testing purposes
providers_1.providers.http.additionalClientOptions = {
verifyServerCertificate: false
};
});
it.each(TLS_VERSIONS)('should successfully create a claim (%s)', async (version) => {
var _a, _b, _c;
providers_1.providers.http.additionalClientOptions = {
...providers_1.providers.http.additionalClientOptions,
supportedProtocolVersions: [version]
};
const user = 'adhiraj';
const result = await (0, client_1.createClaimOnAttestor)({
name: 'http',
params: {
url: claimUrl,
method: 'GET',
responseRedactions: [],
responseMatches: [
{
type: 'contains',
value: `${user}@mock.com`
}
]
},
secretParams: {
authorisationHeader: `Bearer ${user}`
},
ownerPrivateKey: opts.privateKeyHex,
client,
zkEngine,
});
expect(result.error).toBeUndefined();
expect((_a = result.request) === null || _a === void 0 ? void 0 : _a.transcript).toBeTruthy();
// decrypt the transcript and check we didn't accidentally
// leak our secrets in the application data
const transcript = result.request.transcript;
const applMsgs = (0, utils_2.extractApplicationDataFromTranscript)(await (0, server_1.decryptTranscript)(transcript, utils_2.logger, zkEngine, (_b = result.request) === null || _b === void 0 ? void 0 : _b.fixedServerIV, (_c = result.request) === null || _c === void 0 ? void 0 : _c.fixedClientIV));
const requestData = applMsgs
.filter(m => m.sender === 'client')
.map(m => (0, tls_1.uint8ArrayToStr)(m.message))
.join('');
// ensure the secret authorisation header is not leaked
expect(requestData).not.toContain(user);
await expect((0, utils_2.assertValidClaimSignatures)(result, client.metadata)).resolves.toBeUndefined();
expect(mocks_1.SPY_PREPARER).toHaveBeenCalledTimes(1);
// check all direct message reveals and
// ensure we've not accidentally re-used a key
// for multiple application data messages that
// were not meant to be revealed.
await (0, utils_1.verifyNoDirectRevealLeaks)();
});
it('should not create a claim with invalid response', async () => {
await expect(async () => {
await (0, client_1.createClaimOnAttestor)({
name: 'http',
params: {
url: claimUrl,
method: 'GET',
responseRedactions: [],
responseMatches: [
{
type: 'contains',
value: 'something@mock.com'
}
]
},
secretParams: {
authorisationHeader: 'Fail'
},
ownerPrivateKey: opts.privateKeyHex,
client,
zkEngine,
});
}).rejects.toThrow('Provider returned error 401');
});
describe('OPRF via %s', () => {
const zkEngine = 'gnark';
it.each(OPRF_CIPHER_SUITES)('should create a claim with an OPRF redaction (%s)', async (cipherSuite) => {
var _a, _b;
// OPRF is only available on gnark & chacha20 right now
providers_1.providers.http.additionalClientOptions = {
...providers_1.providers.http.additionalClientOptions,
cipherSuites: [cipherSuite]
};
const user = '(?<test>adhiraj)';
const result = await (0, client_1.createClaimOnAttestor)({
name: 'http',
params: {
url: claimUrl,
method: 'GET',
responseRedactions: [
{
regex: user,
hash: 'oprf'
}
],
responseMatches: [
{
type: 'contains',
value: ''
}
]
},
secretParams: {
authorisationHeader: `Bearer ${user}`
},
ownerPrivateKey: opts.privateKeyHex,
client,
zkEngine,
});
expect(result.error).toBeUndefined();
// decrypt the transcript and check we didn't accidentally
// leak our secrets in the application data
const transcript = result.request.transcript;
expect(transcript).toBeTruthy();
const applMsgs = (0, utils_2.extractApplicationDataFromTranscript)(await (0, server_1.decryptTranscript)(transcript, utils_2.logger, zkEngine, (_a = result.request) === null || _a === void 0 ? void 0 : _a.fixedServerIV, (_b = result.request) === null || _b === void 0 ? void 0 : _b.fixedClientIV));
const serverPackets = applMsgs
.filter(m => m.sender === 'server')
.map(m => (0, tls_1.uint8ArrayToStr)(m.message))
.join('');
const toprf = (0, utils_1.getFirstTOprfBlock)(result.request);
expect(toprf).toBeTruthy();
// only the user's hash should be revealed
expect(serverPackets).not.toContain(user);
expect(serverPackets).toContain((0, utils_2.binaryHashToStr)(toprf.nullifier, toprf.dataLocation.length));
});
it('should produce the same hash for the same input', async () => {
let hash;
for (let i = 0; i < 2; i++) {
const user = '(?<su>some-user)';
const result = await (0, client_1.createClaimOnAttestor)({
name: 'http',
params: {
url: claimUrl,
method: 'GET',
responseRedactions: [
{
regex: user,
hash: 'oprf'
}
],
responseMatches: [
{
type: 'contains',
value: ''
}
]
},
secretParams: {
authorisationHeader: `Bearer ${user}`
},
ownerPrivateKey: opts.privateKeyHex,
client,
zkEngine,
});
const toprf = (0, utils_1.getFirstTOprfBlock)(result.request);
expect(toprf).toBeTruthy();
hash || (hash = toprf.nullifier);
expect(toprf.nullifier).toEqual(hash);
}
});
});
describe('Pool', () => {
it('should correctly throw error when tunnel creation fails', async () => {
await expect((0, client_1.createClaimOnAttestor)({
name: 'http',
params: {
url: 'https://some.dns.not.exist',
method: 'GET',
responseRedactions: [],
responseMatches: [
{
type: 'contains',
value: 'test'
}
]
},
secretParams: {
authorisationHeader: 'Bearer abcd'
},
ownerPrivateKey: opts.privateKeyHex,
client: { url: opts.serverUrl },
zkEngine
})).rejects.toMatchObject({
message: /ENOTFOUND/
});
});
it('should reconnect client when found disconnected', async () => {
await createClaim();
// since we're using a pool, we'll find the client
// disconnected and when we create the claim again
// we expect a new connection to be established
const client = (0, client_1.getAttestorClientFromPool)(opts.serverUrl);
await client.terminateConnection();
// ensure claim is still successful
const result2 = await createClaim();
expect(result2.claim).toBeTruthy();
const client2 = (0, client_1.getAttestorClientFromPool)(opts.serverUrl);
expect(client2).not.toBe(client);
});
it('should retry on network errors', async () => {
const client = (0, client_1.getAttestorClientFromPool)(opts.serverUrl);
client.sendMessage = async () => {
// @ts-ignore
client.sendMessage = () => { };
const err = new utils_2.AttestorError('ERROR_NETWORK_ERROR', 'F');
await client.terminateConnection(err);
throw err;
};
// first the client will mock disconnection when
// sending a message -- that should trigger a retry
// and result in a successful claim creation
await expect(createClaim()).resolves.toBeTruthy();
// ensure new client is created to replace
// the disconnected one
const client2 = (0, client_1.getAttestorClientFromPool)(opts.serverUrl);
expect(client2).not.toBe(client);
});
});
function createClaim() {
const user = 'testing-123';
return (0, client_1.createClaimOnAttestor)({
name: 'http',
params: {
url: claimUrl,
method: 'GET',
responseRedactions: [],
responseMatches: [
{
type: 'contains',
value: `${user}@mock.com`
}
]
},
secretParams: {
authorisationHeader: `Bearer ${user}`
},
ownerPrivateKey: opts.privateKeyHex,
client: { url: opts.serverUrl }
});
}
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5jbGFpbS1jcmVhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0cy90ZXN0LmNsYWltLWNyZWF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsOENBQXVGO0FBR3ZGLHVDQUE2RTtBQUM3RSw2Q0FBeUM7QUFDekMsdUNBQThDO0FBQzlDLHlFQUFtRTtBQUNuRSwyQ0FBOEM7QUFDOUMsMkNBQStFO0FBQy9FLHFDQUswQjtBQUUxQixNQUFNLFlBQVksR0FBeUI7SUFDMUMsUUFBUTtJQUNSLFFBQVE7Q0FDUixDQUFBO0FBRUQsTUFBTSxrQkFBa0IsR0FBa0I7SUFDekMsOEJBQThCO0lBQzlCLHdCQUF3QjtJQUN4Qix3QkFBd0I7Q0FDeEIsQ0FBQTtBQUVELElBQUksQ0FBQyxVQUFVLENBQUMsS0FBTSxDQUFDLENBQUE7QUFFdkIsSUFBSSxDQUFDLElBQUksQ0FBQyxrREFBa0QsRUFBRSxHQUFHLEVBQUU7SUFDbEUsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxrREFBa0QsQ0FBQyxDQUFBO0lBQ3JGLE9BQU87UUFDTixVQUFVLEVBQUUsSUFBSTtRQUNoQixHQUFHLE1BQU07UUFDVCxzQkFBc0IsRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUMsa0JBQWtCLEVBQUU7S0FDdEQsQ0FBQTtBQUNGLENBQUMsQ0FBQyxDQUFBO0FBRUYsSUFBQSx5Q0FBa0IsRUFBQyxnQkFBZ0IsRUFBRSxJQUFJLENBQUMsRUFBRTtJQUUzQyxNQUFNLFFBQVEsR0FBYSxPQUFPLENBQUE7SUFFbEMsSUFBSSxNQUFzQixDQUFBO0lBQzFCLElBQUksUUFBZ0IsQ0FBQTtJQUNwQixVQUFVLENBQUMsR0FBRyxFQUFFO1FBQ2YsTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUE7UUFDcEIsUUFBUSxHQUFHLHFCQUFxQixJQUFJLENBQUMsbUJBQW1CLEtBQUssQ0FBQTtRQUU3RCw4Q0FBOEM7UUFDOUMs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