Skip to content

fix(sdk): Remove several exported types #527

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
7 changes: 2 additions & 5 deletions lib/tdf3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ import {
type PemKeyPair,
} from './src/crypto/declarations.js';
import { Client, Errors, TDF3Client } from './src/index.js';
import {
type KeyInfo,
SplitKey,
type EncryptionInformation,
} from './src/models/encryption-information.js';
import { type EncryptionInformation } from './src/models/encryption-information.js';
import { type KeyInfo, SplitKey } from './src/splits.js';
import { AuthProvider, type HttpMethod, HttpRequest, withHeaders } from '../src/auth/auth.js';
import { AesGcmCipher } from './src/ciphers/aes-gcm-cipher.js';
import {
Expand Down
4 changes: 2 additions & 2 deletions lib/tdf3/src/client/builders.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { validateAttribute, validateAttributeObject } from './validation.js';
import { AttributeObject, KeyInfo, Policy } from '../models/index.js';
import { AttributeObject, Policy } from '../models/index.js';
import { type Metadata } from '../tdf.js';
import { Binary } from '../binary.js';

import { KeyInfo } from '../splits.js';
import { ConfigurationError } from '../../../src/errors.js';
import { PemKeyPair } from '../crypto/declarations.js';
import { DecoratedReadableStream } from './DecoratedReadableStream.js';
Expand Down
8 changes: 2 additions & 6 deletions lib/tdf3/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,12 @@ import { Binary } from '../binary.js';
import { AesGcmCipher } from '../ciphers/aes-gcm-cipher.js';
import { toCryptoKeyPair } from '../crypto/crypto-utils.js';
import * as defaultCryptoService from '../crypto/index.js';
import {
type AttributeObject,
type KeyAccessType,
type Policy,
SplitKey,
} from '../models/index.js';
import { type AttributeObject, type KeyAccessType, type Policy } from '../models/index.js';
import { plan } from '../../../src/policy/granter.js';
import { attributeFQNsAsValues } from '../../../src/policy/api.js';
import { type Value } from '../../../src/policy/attributes.js';
import { type Chunker, fromBuffer, fromSource } from '../../../src/seekable.js';
import { SplitKey } from '../splits.js';

const GLOBAL_BYTE_LIMIT = 64 * 1000 * 1000 * 1000; // 64 GB, see WS-9363.

Expand Down
137 changes: 137 additions & 0 deletions lib/tdf3/src/kao-builders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { base64, hex } from '../../src/encodings/index.js';
import { generateRandomNumber } from '../../src/nanotdf-crypto/generateRandomNumber.js';
import { keyAgreement } from '../../src/nanotdf-crypto/keyAgreement.js';
import { pemPublicToCrypto } from '../../src/nanotdf-crypto/pemPublicToCrypto.js';
import { cryptoPublicToPem } from '../../src/utils.js';
import { Binary } from './binary.js';
import * as cryptoService from './crypto/index.js';
import { ztdfSalt } from './crypto/salt.js';
import { type KeyAccessObject, type Policy } from './models/index.js';

export const schemaVersion = '1.0';

export class ECWrapped {
readonly type = 'ec-wrapped';
readonly ephemeralKeyPair: Promise<CryptoKeyPair>;
keyAccessObject?: KeyAccessObject;

constructor(
public readonly url: string,
public readonly kid: string | undefined,
public readonly publicKey: string,
public readonly metadata: unknown,
public readonly sid?: string
) {
this.ephemeralKeyPair = crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256',
},
false,
['deriveBits', 'deriveKey']
);
}

async write(
policy: Policy,
dek: Uint8Array,
encryptedMetadataStr: string
): Promise<KeyAccessObject> {
const policyStr = JSON.stringify(policy);
const [ek, clientPublicKey] = await Promise.all([
this.ephemeralKeyPair,
pemPublicToCrypto(this.publicKey),
]);
const kek = await keyAgreement(ek.privateKey, clientPublicKey, {
hkdfSalt: await ztdfSalt,
hkdfHash: 'SHA-256',
});
const iv = generateRandomNumber(12);
const cek = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, kek, dek);
const entityWrappedKey = new Uint8Array(iv.length + cek.byteLength);
entityWrappedKey.set(iv);
entityWrappedKey.set(new Uint8Array(cek), iv.length);

const policyBinding = await cryptoService.hmac(
hex.encodeArrayBuffer(dek),
base64.encode(policyStr)
);

const ephemeralPublicKeyPEM = await cryptoPublicToPem(ek.publicKey);
const kao: KeyAccessObject = {
type: 'ec-wrapped',
url: this.url,
protocol: 'kas',
wrappedKey: base64.encodeArrayBuffer(entityWrappedKey),
encryptedMetadata: base64.encode(encryptedMetadataStr),
policyBinding: {
alg: 'HS256',
hash: base64.encode(policyBinding),
},
schemaVersion,
ephemeralPublicKey: ephemeralPublicKeyPEM,
};
if (this.kid) {
kao.kid = this.kid;
}
if (this.sid?.length) {
kao.sid = this.sid;
}
this.keyAccessObject = kao;
return kao;
}
}

export class Wrapped {
readonly type = 'wrapped';
keyAccessObject?: KeyAccessObject;

constructor(
public readonly url: string,
public readonly kid: string | undefined,
public readonly publicKey: string,
public readonly metadata: unknown,
public readonly sid?: string
) {}

async write(
policy: Policy,
keyBuffer: Uint8Array,
encryptedMetadataStr: string
): Promise<KeyAccessObject> {
const policyStr = JSON.stringify(policy);
const unwrappedKeyBinary = Binary.fromArrayBuffer(keyBuffer.buffer);
const wrappedKeyBinary = await cryptoService.encryptWithPublicKey(
unwrappedKeyBinary,
this.publicKey
);

const policyBinding = await cryptoService.hmac(
hex.encodeArrayBuffer(keyBuffer),
base64.encode(policyStr)
);

this.keyAccessObject = {
type: 'wrapped',
url: this.url,
protocol: 'kas',
wrappedKey: base64.encode(wrappedKeyBinary.asString()),
encryptedMetadata: base64.encode(encryptedMetadataStr),
policyBinding: {
alg: 'HS256',
hash: base64.encode(policyBinding),
},
schemaVersion,
};
if (this.kid) {
this.keyAccessObject.kid = this.kid;
}
if (this.sid?.length) {
this.keyAccessObject.sid = this.sid;
}

return this.keyAccessObject;
}
}

export type KeyAccess = ECWrapped | Wrapped;
141 changes: 1 addition & 140 deletions lib/tdf3/src/models/encryption-information.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
import { keySplit } from '../utils/index.js';
import { base64, hex } from '../../../src/encodings/index.js';
import { Binary } from '../binary.js';
import { type SymmetricCipher } from '../ciphers/symmetric-cipher-base.js';
import { type KeyAccess, type KeyAccessObject } from './key-access.js';
import { type Policy } from './policy.js';
import {
type CryptoService,
type DecryptResult,
type EncryptResult,
} from '../crypto/declarations.js';
import { type KeyAccessObject } from './key-access.js';
import { IntegrityAlgorithm } from '../tdf.js';
import { ConfigurationError } from '../../../src/errors.js';

export type KeyInfo = {
readonly unwrappedKeyBinary: Binary;
readonly unwrappedKeyIvBinary: Binary;
};

export type Segment = {
readonly hash: string;
Expand Down Expand Up @@ -47,126 +31,3 @@ export type EncryptionInformation = {
};
policy: string;
};

export class SplitKey {
readonly cryptoService: CryptoService;
keyAccess: KeyAccess[];

constructor(public readonly cipher: SymmetricCipher) {
this.cryptoService = cipher.cryptoService;
this.keyAccess = [];
}

async generateKey(): Promise<KeyInfo> {
const unwrappedKey = await this.cipher.generateKey();
const unwrappedKeyBinary = Binary.fromString(hex.decode(unwrappedKey));
const unwrappedKeyIvBinary = await this.generateIvBinary();
return { unwrappedKeyBinary, unwrappedKeyIvBinary };
}

async encrypt(
contentBinary: Binary,
keyBinary: Binary,
ivBinaryOptional?: Binary
): Promise<EncryptResult> {
const ivBinary = ivBinaryOptional || (await this.generateIvBinary());
return this.cipher.encrypt(contentBinary, keyBinary, ivBinary);
}

async decrypt(content: Uint8Array, keyBinary: Binary): Promise<DecryptResult> {
return this.cipher.decrypt(content, keyBinary);
}

async getKeyAccessObjects(policy: Policy, keyInfo: KeyInfo): Promise<KeyAccessObject[]> {
const splitIds = [...new Set(this.keyAccess.map(({ sid }) => sid))].sort((a = '', b = '') =>
a.localeCompare(b)
);
const unwrappedKeySplitBuffers = await keySplit(
new Uint8Array(keyInfo.unwrappedKeyBinary.asByteArray()),
splitIds.length,
this.cryptoService
);
const splitsByName = Object.fromEntries(
splitIds.map((sid, index) => [sid, unwrappedKeySplitBuffers[index]])
);

const keyAccessObjects = [];
for (const item of this.keyAccess) {
// use the key split to encrypt metadata for each key access object
const unwrappedKeySplitBuffer = splitsByName[item.sid || ''];
const unwrappedKeySplitBinary = Binary.fromArrayBuffer(unwrappedKeySplitBuffer.buffer);

const metadata = item.metadata || '';
const metadataStr = (
typeof metadata === 'object'
? JSON.stringify(metadata)
: typeof metadata === 'string'
? metadata
: () => {
throw new ConfigurationError(
"KAO generation failure: metadata isn't a string or object"
);
}
) as string;

const metadataBinary = Binary.fromArrayBuffer(new TextEncoder().encode(metadataStr));

const encryptedMetadataResult = await this.encrypt(
metadataBinary,
unwrappedKeySplitBinary,
keyInfo.unwrappedKeyIvBinary
);

const encryptedMetadataOb = {
ciphertext: base64.encode(encryptedMetadataResult.payload.asString()),
iv: base64.encode(keyInfo.unwrappedKeyIvBinary.asString()),
};

const encryptedMetadataStr = JSON.stringify(encryptedMetadataOb);
const keyAccessObject = await item.write(
policy,
unwrappedKeySplitBuffer,
encryptedMetadataStr
);
keyAccessObjects.push(keyAccessObject);
}

return keyAccessObjects;
}

async generateIvBinary(): Promise<Binary> {
const iv = await this.cipher.generateInitializationVector();
return Binary.fromString(hex.decode(iv));
}

async write(policy: Policy, keyInfo: KeyInfo): Promise<EncryptionInformation> {
const algorithm = this.cipher?.name;
if (!algorithm) {
// Hard coded as part of the cipher object. This should not be reachable.
throw new ConfigurationError('uninitialized cipher type');
}
const keyAccessObjects = await this.getKeyAccessObjects(policy, keyInfo);

// For now we're only concerned with a single (first) key access object
const policyForManifest = base64.encode(JSON.stringify(policy));

return {
type: 'split',
keyAccess: keyAccessObjects,
method: {
algorithm,
isStreamable: false,
iv: base64.encode(keyInfo.unwrappedKeyIvBinary.asString()),
},
integrityInformation: {
rootSignature: {
alg: 'HS256',
sig: '',
},
segmentHashAlg: 'GMAC',
segments: [],
},
policy: policyForManifest,
};
}
}
Loading
Loading