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

✅ round trip test, less rewrap #42

Merged
merged 3 commits into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 32 additions & 37 deletions lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ import { TypedArray, createAttribute, Policy } from './tdf/index';
*
* @example
* ```
* import NanoTDFClient from '@opentdf/client';
* import { clientSecretAuthProvider, NanoTDFClient } from '@opentdf/client';
*
* const OIDC_ENDPOINT = 'http://localhost:65432/keycloak/';
* const KAS_URL = 'http://localhost:65432/kas';
* const OIDC_ENDPOINT = 'http://localhost:65432/auth/';
* const KAS_URL = 'http://localhost:65432/api/kas/';
*
* const ciphertext = '...';
* const client = new NanoTDFClient(
* {
* await clientSecretAuthProvider({
* clientId: 'tdf-client',
* clientSecret: '123-456',
* organizationName: 'tdf',
* oidcOrigin: OIDC_ENDPOINT,
* },
* }),
* KAS_URL
* );
* client.decrypt(ciphertext)
Expand All @@ -38,7 +38,6 @@ import { TypedArray, createAttribute, Policy } from './tdf/index';
* .catch(err => {
* console.error('Some error occurred', err);
* })
* ```
*/
export class NanoTDFClient extends Client {
/**
Expand All @@ -57,15 +56,14 @@ export class NanoTDFClient extends Client {
// TODO: The version number should be fetched from the API
const version = '0.0.1';
// Rewrap key on every request
await this.rewrapKey(
const ukey = await this.rewrapKey(
nanotdf.header.toBuffer(),
nanotdf.header.getKasRewrapUrl(),
nanotdf.header.magicNumberVersion,
version,
nanotdf.header.authTagLength
);

const ukey = this.unwrappedKey;
if (!ukey) {
throw new Error('Key rewrap failure');
}
Expand All @@ -88,15 +86,14 @@ export class NanoTDFClient extends Client {

const legacyVersion = '0.0.0';
// Rewrap key on every request
await this.rewrapKey(
const key = await this.rewrapKey(
nanotdf.header.toBuffer(),
nanotdf.header.getKasRewrapUrl(),
nanotdf.header.magicNumberVersion,
legacyVersion,
nanotdf.header.authTagLength
);

const key = this.unwrappedKey;
if (!key) {
throw new Error('Failed unwrap');
}
Expand Down Expand Up @@ -168,29 +165,26 @@ export class NanoTDFClient extends Client {
*
*
* @example
* import NanoTDFDatasetClient from 'nanotdf-sdk';
* ```
* import { clientSecretAuthProvider, NanoTDFDatasetClient } from '@opentdf/client';
*
* const OIDC_ENDPOINT = 'http://localhost:65432/keycloak/';
* const KAS_URL = 'http://localhost:65432/kas';
* const OIDC_ENDPOINT = 'http://localhost:65432/auth/';
* const KAS_URL = 'http://localhost:65432/api/kas/';
*
* const ciphertext = '...';
* const client = new NanoTDFDatasetClient.default(
* {
* clientId: 'tdf-client',
* clientSecret: '123-456',
* organizationName: 'tdf',
* exchange: 'client',
* oidcOrigin: OIDC_ENDPOINT,
* },
* KAS_URL
* );
* client.decrypt(ciphertext)
* .then(plaintext => {
* console.log('Plaintext', plaintext);
* })
* .catch(err => {
* console.error('Some error occurred', err);
* })
* const ciphertext = '...';
* const client = new NanoTDFDatasetClient.default(
* await clientSecretAuthProvider({
* clientId: 'tdf-client',
* clientSecret: '123-456',
* organizationName: 'tdf',
* exchange: 'client',
* oidcOrigin: OIDC_ENDPOINT,
* }),
* KAS_URL
* );
* const plaintext = client.decrypt(ciphertext);
* console.log('Plaintext', plaintext);
* ```
*/
export class NanoTDFDatasetClient extends Client {
// Total unique IVs(2^24 -1) used for encrypting the nano tdf payloads
Expand All @@ -199,7 +193,8 @@ export class NanoTDFDatasetClient extends Client {

private maxKeyIteration: number;
private keyIterationCount: number;
private cachedEphemmeralKey?: Uint8Array;
private cachedEphemeralKey?: Uint8Array;
private unwrappedKey?: CryptoKey;
private symmetricKey?: CryptoKey;
private cachedHeader?: Header;

Expand Down Expand Up @@ -345,13 +340,13 @@ export class NanoTDFDatasetClient extends Client {
// Parse ciphertext
const nanotdf = NanoTDF.from(ciphertext);

if (!this.cachedEphemmeralKey) {
if (!this.cachedEphemeralKey) {
// First decrypt
return this.rewrapAndDecrypt(nanotdf);
}

// Other encrypts
if (this.cachedEphemmeralKey.toString() == nanotdf.header.ephemeralPublicKey.toString()) {
if (this.cachedEphemeralKey.toString() == nanotdf.header.ephemeralPublicKey.toString()) {
const ukey = this.unwrappedKey;
if (!ukey) {
throw new Error('Key rewrap failure');
Expand All @@ -369,19 +364,19 @@ export class NanoTDFDatasetClient extends Client {

const version = '0.0.1';
// Rewrap key on every request
await this.rewrapKey(
const ukey = await this.rewrapKey(
nanotdf.header.toBuffer(),
nanotdf.header.getKasRewrapUrl(),
nanotdf.header.magicNumberVersion,
version,
nanotdf.header.authTagLength
);
const ukey = this.unwrappedKey;
if (!ukey) {
throw new Error('Key rewrap failure');
}

this.cachedEphemmeralKey = nanotdf.header.ephemeralPublicKey;
this.cachedEphemeralKey = nanotdf.header.ephemeralPublicKey;
this.unwrappedKey = ukey;

// Return decrypt promise
return decrypt(ukey, nanotdf);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/nanotdf-crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export { default as digest } from './digest';
export { default as encrypt } from './encrypt';
export { default as generateKeyPair } from './generateKeyPair';
export { default as importRawKey } from './importRawKey';
export { default as keyAgreement } from './keyAgreement';
export { keyAgreement } from './keyAgreement';
export { default as exportCryptoKey } from './exportCryptoKey';
export { default as generateRandomNumber } from './generateRandomNumber';
export { default as pemPublicToCrypto, extractPublicFromCertToCrypto } from './pemPublicToCrypto';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/nanotdf-crypto/keyAgreement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ interface KeyAgreementOptions {
* - privateKey {CryptoKey} default: "undefined"
* - options {Object} default: { bitLength: 256, hkdfHash: 'SHA-512', hkdfSalt: "new UInt8Array()", hkdfInfo: "new UInt8Array()", keyCipher: 'AES-GCM', keyLength: 256, keyUsages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], isExtractable: true }
*/
export default async function keyAgreement(
export async function keyAgreement(
privateKey: CryptoKey,
publicKey: CryptoKey,
options: Partial<KeyAgreementOptions> = {
Expand Down
15 changes: 3 additions & 12 deletions lib/src/nanotdf/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export default class Client {
dataAttributes: string[] = [];
protected ephemeralKeyPair?: Required<Readonly<CryptoKeyPair>>;
protected requestSignerKeyPair?: Required<Readonly<CryptoKeyPair>>;
protected unwrappedKey?: CryptoKey;
protected iv?: number;

/**
Expand Down Expand Up @@ -130,15 +129,6 @@ export default class Client {
return { publicKey, privateKey };
}

/**
* Get the unwrapped key
*
* Returns the unwrapped key or undefined if not rewrapped
*/
getUnwrappedKey(): CryptoKey | undefined {
return this.unwrappedKey;
}

/**
* Add attribute to the TDF file/data
*
Expand Down Expand Up @@ -285,8 +275,9 @@ export default class Client {
}

// UnwrappedKey
let unwrappedKey;
try {
this.unwrappedKey = await importRawKey(
unwrappedKey = await importRawKey(
decryptedKey,
// Want to use the key to encrypt and decrypt. Signing key will be used later.
[KeyUsageType.Encrypt, KeyUsageType.Decrypt],
Expand All @@ -299,7 +290,7 @@ export default class Client {
throw new Error(`Unable to import raw key.\n Caused by: ${e.message}`);
}

return this.unwrappedKey;
return unwrappedKey;
} catch (e) {
throw new Error(`Could not rewrap key with entity object.\n Caused by: ${e.message}`);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/version.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Exposes the released version number of the `@opentdf/client` package
*/
export const version = process.env.PKG_VERSION;
export const version = typeof process === 'undefined' ? 'main' : process.env.PKG_VERSION;

/**
* A string name used to label requests as coming from this library client.
Expand Down
63 changes: 63 additions & 0 deletions lib/test/nano-roundtrip.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect } from '@esm-bundle/chai';
import sinon from 'sinon';
import { AuthProvider } from '../src/auth/auth.js';

import { NanoTDFClient } from '../src/index.js';

const authProvider = <AuthProvider>{
updateClientPublicKey: async (clientPubKey) => {
/* mocked function */
},
authorization: async () =>
'Bearer dummy-auth-token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZGYiLCJzdWIiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0.XFu4sQxAd6n-b7urqTdQ-I9zKqKSQtC04unHsMSpJjc',
};

const kasPubKey = `-----BEGIN CERTIFICATE-----
MIIBcjCCARegAwIBAgIUHeLWauo8LkzzWtq3alLWzR9Gxt4wCgYIKoZIzj0EAwIw
DjEMMAoGA1UEAwwDa2FzMB4XDTIyMDMwMzE0NDcwMFoXDTIzMDMwMzE0NDcwMFow
DjEMMAoGA1UEAwwDa2FzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEoPCmh6d
boMZLjFLX6Q3gLsiV40e1fpJ9gfm8GXogh0w7c8bb3SkNH4NXZ0YpevkScy2UZOl
KIgSAn70lERIj6NTMFEwHQYDVR0OBBYEFAHmNbtdve05mTWKmHLiTSG49oL1MB8G
A1UdIwQYMBaAFAHmNbtdve05mTWKmHLiTSG49oL1MA8GA1UdEwEB/wQFMAMBAf8w
CgYIKoZIzj0EAwIDSQAwRgIhALJmwqc6xYQKu84GOjz+P4WmBHEoGvqT2ZWXnLuZ
5jKMAiEAmdiam2/jiDTt38PKQkkJvqTlOogTMnigGBOE+FuuB2M=
-----END CERTIFICATE-----`;

function mockApiResponse(status = 200, body = {}) {
return new globalThis.Response(JSON.stringify(body), {
status,
headers: { 'Content-type': 'application/json' },
});
}

function initSandbox() {
const sandbox = sinon.createSandbox();
const fetchLives = sandbox.stub(globalThis, 'fetch');
fetchLives.callsFake(async (resource, init) => {
if (resource === 'http://localhost:65432/api/kas/kas_public_key?algorithm=ec:secp256r1') {
return mockApiResponse(200, kasPubKey);
}
console.log(`trying to fetch( resource: [${resource}], init:`, init);
return mockApiResponse(404);
});
return sandbox;
}

const kasUrl = 'http://localhost:65432/api/kas';

describe('Local roundtrip Tests', () => {
it('roundtrip string', async () => {
// const sandbox = initSandbox();
const sandbox = initSandbox();
try {
const client = new NanoTDFClient(authProvider, kasUrl);
const keyAgreementSpy = sandbox.spy(globalThis.crypto.subtle, 'deriveKey');
sandbox.stub(client, 'rewrapKey').callsFake(async () => keyAgreementSpy.lastCall.returnValue);
const cipherText = await client.encrypt('hello world');
const actual = await client.decrypt(cipherText);
expect(new TextDecoder().decode(actual)).to.be.equal('hello world');
} finally {
sandbox.reset();
}
});
});
2 changes: 1 addition & 1 deletion lib/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSourceMap": false,
"inlineSourceMap": true,
"lib": ["dom", "dom.iterable", "es2020"],
"module": "esnext",
"moduleResolution": "node",
Expand Down