Skip to content

Commit 7a94687

Browse files
committed
feat(sdk): plain text policy support
1 parent f0d9719 commit 7a94687

File tree

6 files changed

+65
-26
lines changed

6 files changed

+65
-26
lines changed

cli/src/cli.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { webcrypto } from 'crypto';
2323
import * as assertions from '@opentdf/sdk/assertions';
2424
import { attributeFQNsAsValues } from '@opentdf/sdk/nano';
2525
import { base64 } from '@opentdf/sdk/encodings';
26+
import { PolicyType} from '@opentdf/sdk';
2627
import { type CryptoKey, importPKCS8, importSPKI } from 'jose'; // for RS256
2728

2829
type AuthToProcess = {
@@ -309,12 +310,16 @@ async function parseCreateZTDFOptions(argv: Partial<mainArgs>): Promise<CreateZT
309310
return c;
310311
}
311312

312-
async function parseCreateNanoTDFOptions(argv: Partial<mainArgs>): Promise<CreateZTDFOptions> {
313+
async function parseCreateNanoTDFOptions(argv: Partial<mainArgs>): Promise<CreateNanoTDFOptions> {
313314
const c: CreateNanoTDFOptions = await parseCreateOptions(argv);
314315
const ecdsaBinding = argv.policyBinding?.toLowerCase() == 'ecdsa';
315316
if (ecdsaBinding) {
316317
c.bindingType = 'ecdsa';
317318
}
319+
320+
if (argv.policyType) {
321+
c.policyType = PolicyType[argv.policyType as keyof typeof PolicyType]
322+
}
318323
// NOTE autoconfigure is not yet supported in nanotdf
319324
delete c.autoconfigure;
320325
log('DEBUG', `CreateNanoTDFOptions: ${JSON.stringify(c)}`);
@@ -521,6 +526,13 @@ export const handleArgs = (args: string[]) => {
521526
description: 'TDF spec version for file creation',
522527
default: tdfSpecVersion,
523528
},
529+
policyType: {
530+
group: 'Encrypt Options:',
531+
desc: 'Policy type for NanoTDF: EmbeddedEncrypted or EmbeddedText',
532+
type: 'string',
533+
choices: ['EmbeddedEncrypted', 'EmbeddedText'],
534+
default: 'EmbeddedEncrypted',
535+
},
524536
})
525537

526538
.options({

lib/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export { PlatformClient, type PlatformClientOptions, type PlatformServices } fro
66
export * from './opentdf.js';
77
export * from './seekable.js';
88
export * from '../tdf3/src/models/index.js';
9+
export { default as PolicyType } from './nanotdf/enum/PolicyTypeEnum.js';

lib/src/nanoclients.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ import { fetchECKasPubKey } from './access.js';
1414
import { type ClientConfig } from './nanotdf/Client.js';
1515
import { ConfigurationError } from './errors.js';
1616
import { type AttributeObject } from '../tdf3/src/models/attribute.js';
17+
import PolicyType from './nanotdf/enum/PolicyTypeEnum.js';
1718

1819
// Define the EncryptOptions type
1920
export type EncryptOptions = {
2021
ecdsaBinding: boolean;
22+
policyType?: PolicyType;
2123
};
2224

2325
// Define default options
2426
const defaultOptions: EncryptOptions = {
2527
ecdsaBinding: false,
28+
policyType: PolicyType.EmbeddedEncrypted,
2629
};
2730

2831
/**
@@ -100,6 +103,14 @@ export class NanoTDFClient extends Client {
100103
const ephemeralKeyPair = await this.ephemeralKeyPair;
101104
const initializationVector = this.iv;
102105

106+
const mergedOptions: EncryptOptions = { ...defaultOptions, ...options };
107+
if (
108+
mergedOptions.policyType !== PolicyType.EmbeddedEncrypted &&
109+
mergedOptions.policyType !== PolicyType.EmbeddedText
110+
) {
111+
throw new ConfigurationError('Invalid policy type');
112+
}
113+
103114
if (typeof initializationVector !== 'number') {
104115
throw new ConfigurationError(
105116
'NanoTDF clients are single use. Please generate a new client and keypair.'
@@ -146,14 +157,14 @@ export class NanoTDFClient extends Client {
146157
payloadIV[10] = lengthAsUint24[1];
147158
payloadIV[11] = lengthAsUint24[0];
148159

149-
const mergedOptions: EncryptOptions = { ...defaultOptions, ...options };
150160
return encrypt(
151161
policyObjectAsStr,
152162
this.kasPubKey,
153163
ephemeralKeyPair,
154164
payloadIV,
155165
data,
156-
mergedOptions.ecdsaBinding
166+
mergedOptions.ecdsaBinding,
167+
mergedOptions.policyType
157168
);
158169
}
159170
}
@@ -283,7 +294,8 @@ export class NanoTDFDatasetClient extends Client {
283294
ephemeralKeyPair,
284295
ivVector,
285296
data,
286-
this.ecdsaBinding
297+
this.ecdsaBinding,
298+
mergedOptions.policyType,
287299
);
288300

289301
// Cache the header and increment the key iteration

lib/src/nanotdf/encrypt.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { KasPublicKeyInfo } from '../access.js';
1919
import { computeECDSASig, extractRSValuesFromSignature } from '../nanotdf-crypto/ecdsaSignature.js';
2020
import { ConfigurationError } from '../errors.js';
21+
import PolicyType from './enum/PolicyTypeEnum.js';
2122

2223
/**
2324
* Encrypt the plain data into nanotdf buffer
@@ -28,14 +29,16 @@ import { ConfigurationError } from '../errors.js';
2829
* @param iv
2930
* @param data The data to be encrypted
3031
* @param ecdsaBinding Flag to enable ECDSA binding
32+
* @param policyType Policy type to use for the nanotdf
3133
*/
3234
export default async function encrypt(
3335
policy: string,
3436
kasInfo: KasPublicKeyInfo,
3537
ephemeralKeyPair: CryptoKeyPair,
3638
iv: Uint8Array,
3739
data: string | ArrayBufferLike,
38-
ecdsaBinding: boolean = DefaultParams.ecdsaBinding
40+
ecdsaBinding: boolean = DefaultParams.ecdsaBinding,
41+
policyType?: PolicyType
3942
): Promise<ArrayBuffer> {
4043
// Generate a symmetric key.
4144
if (!ephemeralKeyPair.privateKey) {
@@ -54,23 +57,32 @@ export default async function encrypt(
5457
// Auth tag length for policy and payload
5558
const authTagLengthInBytes = authTagLengthForCipher(DefaultParams.symmetricCipher) / 8;
5659

57-
// Encrypt the policy
58-
const policyIV = new Uint8Array(iv.length).fill(0);
59-
const policyAsBuffer = new TextEncoder().encode(policy);
60-
const encryptedPolicy = await cryptoEncrypt(
61-
symmetricKey,
62-
policyAsBuffer,
63-
policyIV,
64-
authTagLengthInBytes * 8
65-
);
60+
let policyContent: Uint8Array;
61+
if (policyType === PolicyType.EmbeddedText) {
62+
// Store policy as plain text
63+
policyContent = new TextEncoder().encode(policy);
64+
} else {
65+
// Encrypt the policy
66+
const policyIV = new Uint8Array(iv.length).fill(0);
67+
const policyAsBuffer = new TextEncoder().encode(policy);
68+
policyContent = new Uint8Array(
69+
await cryptoEncrypt(
70+
symmetricKey,
71+
policyAsBuffer,
72+
policyIV,
73+
authTagLengthInBytes * 8
74+
)
75+
);
76+
}
77+
6678

6779
let policyBinding: Uint8Array;
6880

6981
// Calculate the policy binding.
7082
if (ecdsaBinding) {
7183
const curveName = await getCurveNameFromPrivateKey(ephemeralKeyPair.privateKey);
7284
const ecdsaPrivateKey = await convertECDHToECDSA(ephemeralKeyPair.privateKey, curveName);
73-
const ecdsaSignature = await computeECDSASig(ecdsaPrivateKey, new Uint8Array(encryptedPolicy));
85+
const ecdsaSignature = await computeECDSASig(ecdsaPrivateKey, policyContent);
7486
const { r, s } = extractRSValuesFromSignature(new Uint8Array(ecdsaSignature));
7587

7688
const rLength = r.length;
@@ -84,15 +96,15 @@ export default async function encrypt(
8496
policyBinding[1 + rLength] = sLength;
8597
policyBinding.set(s, 1 + rLength + 1);
8698
} else {
87-
const signature = await digest('SHA-256', new Uint8Array(encryptedPolicy));
99+
const signature = await digest('SHA-256', policyContent);
88100
policyBinding = new Uint8Array(signature.slice(-GMAC_BINDING_LEN));
89101
}
90102

91103
// Create embedded policy
92104
const embeddedPolicy = new EmbeddedPolicy(
93-
DefaultParams.policyType,
105+
policyType ?? PolicyType.EmbeddedEncrypted,
94106
policyBinding,
95-
new Uint8Array(encryptedPolicy)
107+
policyContent
96108
);
97109

98110
if (!ephemeralKeyPair.publicKey) {

lib/src/nanotdf/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export { default as encrypt } from './encrypt.js';
88
export { default as encryptDataset } from './encrypt-dataset.js';
99
export { default as getHkdfSalt } from './helpers/getHkdfSalt.js';
1010
export { default as DefaultParams } from './models/DefaultParams.js';
11+
export { default as PolicyType } from './enum/PolicyTypeEnum.js';

lib/src/opentdf.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ export type CreateNanoTDFOptions = CreateOptions & {
9292
* to generate a signature for each element. When absent, the nanotdf is unsigned.
9393
*/
9494
signingKeyID?: string;
95+
96+
/** The type of policy to embed in the NanoTDF. */
97+
policyType?: PolicyType;
9598
};
9699

97100
/** Options for creating a NanoTDF collection. */
@@ -759,14 +762,12 @@ class Collection {
759762
if (opts.ecdsaBindingKeyID) {
760763
throw new ConfigurationError('custom binding key not implemented');
761764
}
762-
switch (opts.bindingType) {
763-
case 'ecdsa':
764-
this.encryptOptions = { ecdsaBinding: true };
765-
break;
766-
case 'gmac':
767-
this.encryptOptions = { ecdsaBinding: false };
768-
break;
769-
}
765+
766+
// Initialize encryptOptions with policyType if provided
767+
this.encryptOptions = {
768+
ecdsaBinding: opts.bindingType === 'ecdsa',
769+
policyType: opts.policyType
770+
};
770771

771772
const kasEndpoint =
772773
opts.defaultKASEndpoint || opts.platformUrl || 'https://disallow.all.invalid';

0 commit comments

Comments
 (0)