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

feat(cli): Assertion signing keys and verification #398

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6475c4f
update the cli to support assertions with signing keys
elizabethhealy Dec 9, 2024
a0a609a
🤖 🎨 Autoformat
elizabethhealy Dec 9, 2024
24c6ce0
add await
elizabethhealy Dec 9, 2024
d711b00
Merge branch 'assertion-verification-cli' of https://github.com/opent…
elizabethhealy Dec 9, 2024
95a23f7
add support for assertion verification keys
elizabethhealy Dec 9, 2024
4a5c453
🤖 🎨 Autoformat
elizabethhealy Dec 9, 2024
0e59845
add default
elizabethhealy Dec 9, 2024
fe5d1cf
Merge branch 'assertion-verification-cli' of https://github.com/opent…
elizabethhealy Dec 9, 2024
e80c461
fix ts errors
elizabethhealy Dec 9, 2024
57c5e88
🤖 🎨 Autoformat
elizabethhealy Dec 9, 2024
81e7ac3
fix assertion verification structure validation
elizabethhealy Dec 10, 2024
b5ebb0c
🤖 🎨 Autoformat
elizabethhealy Dec 10, 2024
1a02f05
uppercase keys
elizabethhealy Dec 10, 2024
a088409
🤖 🎨 Autoformat
elizabethhealy Dec 10, 2024
6da3366
allow for passing file instead, avoid keys in command line
elizabethhealy Dec 12, 2024
f950025
🤖 🎨 Autoformat
elizabethhealy Dec 12, 2024
1d59b4b
typo fix
elizabethhealy Dec 12, 2024
1be9f23
Merge branch 'main' into assertion-verification-cli
elizabethhealy Dec 13, 2024
f33b913
fix(cli): Enables concurrent rewrap in cli (#391)
dmihalcik-virtru Nov 18, 2024
cd71d64
update the cli to support assertions with signing keys
elizabethhealy Dec 9, 2024
a58a6bf
add await
elizabethhealy Dec 9, 2024
44bc2f6
🤖 🎨 Autoformat
elizabethhealy Dec 9, 2024
f1048d5
add support for assertion verification keys
elizabethhealy Dec 9, 2024
df2dbec
add default
elizabethhealy Dec 9, 2024
2492b5c
🤖 🎨 Autoformat
elizabethhealy Dec 9, 2024
24ac166
fix ts errors
elizabethhealy Dec 9, 2024
eeac9e0
🤖 🎨 Autoformat
elizabethhealy Dec 9, 2024
e00e58a
fix assertion verification structure validation
elizabethhealy Dec 10, 2024
73907d6
🤖 🎨 Autoformat
elizabethhealy Dec 10, 2024
3701191
uppercase keys
elizabethhealy Dec 10, 2024
73ccd7b
🤖 🎨 Autoformat
elizabethhealy Dec 10, 2024
2a6f065
allow for passing file instead, avoid keys in command line
elizabethhealy Dec 12, 2024
e98cabf
🤖 🎨 Autoformat
elizabethhealy Dec 12, 2024
decd1c6
typo fix
elizabethhealy Dec 12, 2024
d002896
chore!(sdk): remove v1 support (#393)
dmihalcik-virtru Dec 10, 2024
891fe86
🆙 0.2.0 minor autobump (#400)
dmihalcik-virtru Dec 11, 2024
5dd9610
chore(sdk): Prefer named, direct exports (#401)
dmihalcik-virtru Dec 11, 2024
bf53a46
chore(sdk): Removes base64-js dep (#402)
dmihalcik-virtru Dec 12, 2024
6194a13
feat!(sdk): Remove 'toFile' method (#403)
dmihalcik-virtru Dec 12, 2024
3404b5d
chore(sdk): Refactor old clients to own file (#405)
dmihalcik-virtru Dec 13, 2024
9831b91
Merge branch 'assertion-verification-cli' of https://github.com/opent…
elizabethhealy Dec 13, 2024
fefda54
Merge branch 'main' into assertion-verification-cli
dmihalcik-virtru Dec 13, 2024
794ed45
Merge branch 'main' into assertion-verification-cli
elizabethhealy Dec 16, 2024
05b905d
suggested changes
elizabethhealy Dec 16, 2024
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
100 changes: 97 additions & 3 deletions cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { webcrypto } from 'crypto';
import * as assertions from '@opentdf/sdk/assertions';
import { attributeFQNsAsValues } from '@opentdf/sdk/nano';
import { base64 } from '@opentdf/sdk/encodings';
import { importPKCS8, importSPKI, KeyLike } from 'jose'; // for RS256
import { TextEncoder } from 'util'; // for HS256

type AuthToProcess = {
auth?: string;
Expand Down Expand Up @@ -120,11 +122,53 @@ function addParams(client: AnyNanoClient, argv: Partial<mainArgs>) {
log('SILLY', `Built encrypt params dissems: ${client.dissems}, attrs: ${client.dataAttributes}`);
}

async function parseAssertionVerificationKeys(
s: string
): Promise<assertions.AssertionVerificationKeys> {
const u = JSON.parse(s);
if (typeof u !== 'object' || u === null) {
throw new Error('Invalid input: The input must be an object');
}
// handle both cases of "keys"
if (!('Keys' in u && typeof u.Keys === 'object')) {
if ('keys' in u && typeof u.keys === 'object') {
u.Keys = u.keys;
} else {
throw new Error('Invalid input: invalid structure of assertionVerificationKeys');
}
}
for (const assertionName in u.Keys) {
const assertionKey = u.Keys[assertionName];
// Ensure each entry has the required 'key' and 'alg' fields
if (typeof assertionKey !== 'object' || assertionKey === null) {
throw new CLIError('CRITICAL', `Invalid assertion for ${assertionName}: Must be an object`);
}

if (typeof assertionKey.key !== 'string' || typeof assertionKey.alg !== 'string') {
throw new CLIError(
'CRITICAL',
`Invalid assertion for ${assertionName}: Missing or invalid 'key' or 'alg'`
);
}
try {
u.Keys[assertionName].key = await correctAssertionKeys(assertionKey.alg, assertionKey.key);
} catch (err) {
throw new CLIError('CRITICAL', `Issue converting assertion key from string: ${err.message}`);
}
}
return u;
}

async function tdf3DecryptParamsFor(argv: Partial<mainArgs>): Promise<DecryptParams> {
const c = new DecryptParamsBuilder();
if (argv.noVerifyAssertions) {
c.withNoVerifyAssertions(true);
}
if (argv.assertionVerificationKeys) {
c.withAssertionVerificaitonKeys(
await parseAssertionVerificationKeys(argv.assertionVerificationKeys)
);
}
if (argv.concurrencyLimit) {
c.withConcurrencyLimit(argv.concurrencyLimit);
} else {
Expand All @@ -134,25 +178,67 @@ async function tdf3DecryptParamsFor(argv: Partial<mainArgs>): Promise<DecryptPar
return c.build();
}

function parseAssertionConfig(s: string): assertions.AssertionConfig[] {
async function correctAssertionKeys(
alg: string,
key: KeyLike | Uint8Array
): Promise<KeyLike | Uint8Array> {
if (alg === 'HS256') {
// Convert key string to Uint8Array
if (typeof key !== 'string') {
throw new CLIError('CRITICAL', 'HS256 key must be a string');
}
return new TextEncoder().encode(key); // Update array element directly
} else if (alg === 'RS256') {
// Convert PEM string to a KeyLike object
if (typeof key !== 'string') {
throw new CLIError('CRITICAL', 'RS256 key must be a PEM string');
}
try {
return await importPKCS8(key, 'RS256'); // Import private key
} catch (err) {
// If importing as a private key fails, try importing as a public key
try {
return await importSPKI(key, 'RS256'); // Import public key
} catch (err) {
throw new CLIError('CRITICAL', `Failed to parse RS256 key: ${err.message}`);
}
}
}
// Otherwise its an unsupported alg
throw new CLIError('CRITICAL', `Unsupported signing key algorithm: ${alg}`); // Handle unsupported algs
}

async function parseAssertionConfig(s: string): Promise<assertions.AssertionConfig[]> {
const u = JSON.parse(s);
// if u is null or empty, return an empty array
if (!u) {
return [];
}
const a = Array.isArray(u) ? u : [u];
for (const assertion of a) {
for (let i = 0; i < a.length; i++) {
const assertion = a[i];
if (!assertions.isAssertionConfig(assertion)) {
throw new CLIError('CRITICAL', `invalid assertion config ${JSON.stringify(assertion)}`);
}
if (assertion.signingKey) {
const { alg, key } = assertion.signingKey;
try {
a[i].signingKey.key = await correctAssertionKeys(alg, key);
} catch (err) {
throw new CLIError(
'CRITICAL',
`Issue converting assertion key from string: ${err.message}`
);
}
}
}
return a;
}

async function tdf3EncryptParamsFor(argv: Partial<mainArgs>): Promise<EncryptParams> {
const c = new EncryptParamsBuilder();
if (argv.assertions?.length) {
c.withAssertions(parseAssertionConfig(argv.assertions));
c.withAssertions(await parseAssertionConfig(argv.assertions));
}
if (argv.attributes?.length) {
c.setAttributes(argv.attributes.split(','));
Expand Down Expand Up @@ -249,6 +335,14 @@ export const handleArgs = (args: string[]) => {
desc: 'Do not verify assertions',
type: 'boolean',
})
.option('assertionVerificationKeys', {
alias: 'with-assertion-verification-keys',
group: 'Decrypt',
desc: 'keys for assertion verification',
type: 'string',
default: '',
validate: parseAssertionVerificationKeys,
})
.option('concurrencyLimit', {
alias: 'concurrency-limit',
group: 'Decrypt',
Expand Down
13 changes: 13 additions & 0 deletions lib/tdf3/src/client/builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,19 @@ class DecryptParamsBuilder {
return this;
}

/**
* Sets the assertion verification keys for the decryption parameters.
*
* @param {AssertionVerificationKeys} assertionVerificationKeys - An array of assertion configurations to be set.
* @returns {DecryptParamsBuilder} The current instance of the EncryptParamsBuilder for method chaining.
*/
withAssertionVerificaitonKeys(
assertionVerificationKeys: AssertionVerificationKeys
): DecryptParamsBuilder {
this._params.assertionVerificationKeys = assertionVerificationKeys;
return this;
}

_deepCopy(_params: DecryptParams) {
return freeze({ ..._params });
}
Expand Down
2 changes: 1 addition & 1 deletion lib/tests/mocha/unit/builders.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const aex = {
pubKey: 'PUBKEY',
};

describe('EncyptParamsBuilder', () => {
describe('EncryptParamsBuilder', () => {
describe('setAttributes', () => {
it('should accept valid attribute', () => {
const paramsBuilder = new EncryptParamsBuilder();
Expand Down