Skip to content

Commit 5909d8c

Browse files
fix(sdk): Remove several exported types
- Moves some package-private types out of the `models` subpackage, which is by-default exported in src/index.js
1 parent f769a2c commit 5909d8c

File tree

11 files changed

+291
-306
lines changed

11 files changed

+291
-306
lines changed

lib/tdf3/index.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ import {
2020
type PemKeyPair,
2121
} from './src/crypto/declarations.js';
2222
import { Client, Errors, TDF3Client } from './src/index.js';
23-
import {
24-
type KeyInfo,
25-
SplitKey,
26-
type EncryptionInformation,
27-
} from './src/models/encryption-information.js';
23+
import { type EncryptionInformation } from './src/models/encryption-information.js';
24+
import { type KeyInfo, SplitKey } from './src/splits.js';
2825
import { AuthProvider, type HttpMethod, HttpRequest, withHeaders } from '../src/auth/auth.js';
2926
import { AesGcmCipher } from './src/ciphers/aes-gcm-cipher.js';
3027
import {

lib/tdf3/src/client/builders.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { validateAttribute, validateAttributeObject } from './validation.js';
2-
import { AttributeObject, KeyInfo, Policy } from '../models/index.js';
2+
import { AttributeObject, Policy } from '../models/index.js';
33
import { type Metadata } from '../tdf.js';
44
import { Binary } from '../binary.js';
5-
5+
import { KeyInfo } from '../splits.js';
66
import { ConfigurationError } from '../../../src/errors.js';
77
import { PemKeyPair } from '../crypto/declarations.js';
88
import { DecoratedReadableStream } from './DecoratedReadableStream.js';

lib/tdf3/src/client/index.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,12 @@ import { Binary } from '../binary.js';
4848
import { AesGcmCipher } from '../ciphers/aes-gcm-cipher.js';
4949
import { toCryptoKeyPair } from '../crypto/crypto-utils.js';
5050
import * as defaultCryptoService from '../crypto/index.js';
51-
import {
52-
type AttributeObject,
53-
type KeyAccessType,
54-
type Policy,
55-
SplitKey,
56-
} from '../models/index.js';
51+
import { type AttributeObject, type KeyAccessType, type Policy } from '../models/index.js';
5752
import { plan } from '../../../src/policy/granter.js';
5853
import { attributeFQNsAsValues } from '../../../src/policy/api.js';
5954
import { type Value } from '../../../src/policy/attributes.js';
6055
import { type Chunker, fromBuffer, fromSource } from '../../../src/seekable.js';
56+
import { SplitKey } from '../splits.js';
6157

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

lib/tdf3/src/kao-builders.ts

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { base64, hex } from '../../src/encodings/index.js';
2+
import { generateRandomNumber } from '../../src/nanotdf-crypto/generateRandomNumber.js';
3+
import { keyAgreement } from '../../src/nanotdf-crypto/keyAgreement.js';
4+
import { pemPublicToCrypto } from '../../src/nanotdf-crypto/pemPublicToCrypto.js';
5+
import { cryptoPublicToPem } from '../../src/utils.js';
6+
import { Binary } from './binary.js';
7+
import * as cryptoService from './crypto/index.js';
8+
import { ztdfSalt } from './crypto/salt.js';
9+
import { type KeyAccessObject, type Policy } from './models/index.js';
10+
11+
export const schemaVersion = '1.0';
12+
13+
export class ECWrapped {
14+
readonly type = 'ec-wrapped';
15+
readonly ephemeralKeyPair: Promise<CryptoKeyPair>;
16+
keyAccessObject?: KeyAccessObject;
17+
18+
constructor(
19+
public readonly url: string,
20+
public readonly kid: string | undefined,
21+
public readonly publicKey: string,
22+
public readonly metadata: unknown,
23+
public readonly sid?: string
24+
) {
25+
this.ephemeralKeyPair = crypto.subtle.generateKey(
26+
{
27+
name: 'ECDH',
28+
namedCurve: 'P-256',
29+
},
30+
false,
31+
['deriveBits', 'deriveKey']
32+
);
33+
}
34+
35+
async write(
36+
policy: Policy,
37+
dek: Uint8Array,
38+
encryptedMetadataStr: string
39+
): Promise<KeyAccessObject> {
40+
const policyStr = JSON.stringify(policy);
41+
const [ek, clientPublicKey] = await Promise.all([
42+
this.ephemeralKeyPair,
43+
pemPublicToCrypto(this.publicKey),
44+
]);
45+
const kek = await keyAgreement(ek.privateKey, clientPublicKey, {
46+
hkdfSalt: await ztdfSalt,
47+
hkdfHash: 'SHA-256',
48+
});
49+
const iv = generateRandomNumber(12);
50+
const cek = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, kek, dek);
51+
const entityWrappedKey = new Uint8Array(iv.length + cek.byteLength);
52+
entityWrappedKey.set(iv);
53+
entityWrappedKey.set(new Uint8Array(cek), iv.length);
54+
55+
const policyBinding = await cryptoService.hmac(
56+
hex.encodeArrayBuffer(dek),
57+
base64.encode(policyStr)
58+
);
59+
60+
const ephemeralPublicKeyPEM = await cryptoPublicToPem(ek.publicKey);
61+
const kao: KeyAccessObject = {
62+
type: 'ec-wrapped',
63+
url: this.url,
64+
protocol: 'kas',
65+
wrappedKey: base64.encodeArrayBuffer(entityWrappedKey),
66+
encryptedMetadata: base64.encode(encryptedMetadataStr),
67+
policyBinding: {
68+
alg: 'HS256',
69+
hash: base64.encode(policyBinding),
70+
},
71+
schemaVersion,
72+
ephemeralPublicKey: ephemeralPublicKeyPEM,
73+
};
74+
if (this.kid) {
75+
kao.kid = this.kid;
76+
}
77+
if (this.sid?.length) {
78+
kao.sid = this.sid;
79+
}
80+
this.keyAccessObject = kao;
81+
return kao;
82+
}
83+
}
84+
85+
export class Wrapped {
86+
readonly type = 'wrapped';
87+
keyAccessObject?: KeyAccessObject;
88+
89+
constructor(
90+
public readonly url: string,
91+
public readonly kid: string | undefined,
92+
public readonly publicKey: string,
93+
public readonly metadata: unknown,
94+
public readonly sid?: string
95+
) {}
96+
97+
async write(
98+
policy: Policy,
99+
keyBuffer: Uint8Array,
100+
encryptedMetadataStr: string
101+
): Promise<KeyAccessObject> {
102+
const policyStr = JSON.stringify(policy);
103+
const unwrappedKeyBinary = Binary.fromArrayBuffer(keyBuffer.buffer);
104+
const wrappedKeyBinary = await cryptoService.encryptWithPublicKey(
105+
unwrappedKeyBinary,
106+
this.publicKey
107+
);
108+
109+
const policyBinding = await cryptoService.hmac(
110+
hex.encodeArrayBuffer(keyBuffer),
111+
base64.encode(policyStr)
112+
);
113+
114+
this.keyAccessObject = {
115+
type: 'wrapped',
116+
url: this.url,
117+
protocol: 'kas',
118+
wrappedKey: base64.encode(wrappedKeyBinary.asString()),
119+
encryptedMetadata: base64.encode(encryptedMetadataStr),
120+
policyBinding: {
121+
alg: 'HS256',
122+
hash: base64.encode(policyBinding),
123+
},
124+
schemaVersion,
125+
};
126+
if (this.kid) {
127+
this.keyAccessObject.kid = this.kid;
128+
}
129+
if (this.sid?.length) {
130+
this.keyAccessObject.sid = this.sid;
131+
}
132+
133+
return this.keyAccessObject;
134+
}
135+
}
136+
137+
export type KeyAccess = ECWrapped | Wrapped;
+1-140
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,5 @@
1-
import { keySplit } from '../utils/index.js';
2-
import { base64, hex } from '../../../src/encodings/index.js';
3-
import { Binary } from '../binary.js';
4-
import { type SymmetricCipher } from '../ciphers/symmetric-cipher-base.js';
5-
import { type KeyAccess, type KeyAccessObject } from './key-access.js';
6-
import { type Policy } from './policy.js';
7-
import {
8-
type CryptoService,
9-
type DecryptResult,
10-
type EncryptResult,
11-
} from '../crypto/declarations.js';
1+
import { type KeyAccessObject } from './key-access.js';
122
import { IntegrityAlgorithm } from '../tdf.js';
13-
import { ConfigurationError } from '../../../src/errors.js';
14-
15-
export type KeyInfo = {
16-
readonly unwrappedKeyBinary: Binary;
17-
readonly unwrappedKeyIvBinary: Binary;
18-
};
193

204
export type Segment = {
215
readonly hash: string;
@@ -47,126 +31,3 @@ export type EncryptionInformation = {
4731
};
4832
policy: string;
4933
};
50-
51-
export class SplitKey {
52-
readonly cryptoService: CryptoService;
53-
keyAccess: KeyAccess[];
54-
55-
constructor(public readonly cipher: SymmetricCipher) {
56-
this.cryptoService = cipher.cryptoService;
57-
this.keyAccess = [];
58-
}
59-
60-
async generateKey(): Promise<KeyInfo> {
61-
const unwrappedKey = await this.cipher.generateKey();
62-
const unwrappedKeyBinary = Binary.fromString(hex.decode(unwrappedKey));
63-
const unwrappedKeyIvBinary = await this.generateIvBinary();
64-
return { unwrappedKeyBinary, unwrappedKeyIvBinary };
65-
}
66-
67-
async encrypt(
68-
contentBinary: Binary,
69-
keyBinary: Binary,
70-
ivBinaryOptional?: Binary
71-
): Promise<EncryptResult> {
72-
const ivBinary = ivBinaryOptional || (await this.generateIvBinary());
73-
return this.cipher.encrypt(contentBinary, keyBinary, ivBinary);
74-
}
75-
76-
async decrypt(content: Uint8Array, keyBinary: Binary): Promise<DecryptResult> {
77-
return this.cipher.decrypt(content, keyBinary);
78-
}
79-
80-
async getKeyAccessObjects(policy: Policy, keyInfo: KeyInfo): Promise<KeyAccessObject[]> {
81-
const splitIds = [...new Set(this.keyAccess.map(({ sid }) => sid))].sort((a = '', b = '') =>
82-
a.localeCompare(b)
83-
);
84-
const unwrappedKeySplitBuffers = await keySplit(
85-
new Uint8Array(keyInfo.unwrappedKeyBinary.asByteArray()),
86-
splitIds.length,
87-
this.cryptoService
88-
);
89-
const splitsByName = Object.fromEntries(
90-
splitIds.map((sid, index) => [sid, unwrappedKeySplitBuffers[index]])
91-
);
92-
93-
const keyAccessObjects = [];
94-
for (const item of this.keyAccess) {
95-
// use the key split to encrypt metadata for each key access object
96-
const unwrappedKeySplitBuffer = splitsByName[item.sid || ''];
97-
const unwrappedKeySplitBinary = Binary.fromArrayBuffer(unwrappedKeySplitBuffer.buffer);
98-
99-
const metadata = item.metadata || '';
100-
const metadataStr = (
101-
typeof metadata === 'object'
102-
? JSON.stringify(metadata)
103-
: typeof metadata === 'string'
104-
? metadata
105-
: () => {
106-
throw new ConfigurationError(
107-
"KAO generation failure: metadata isn't a string or object"
108-
);
109-
}
110-
) as string;
111-
112-
const metadataBinary = Binary.fromArrayBuffer(new TextEncoder().encode(metadataStr));
113-
114-
const encryptedMetadataResult = await this.encrypt(
115-
metadataBinary,
116-
unwrappedKeySplitBinary,
117-
keyInfo.unwrappedKeyIvBinary
118-
);
119-
120-
const encryptedMetadataOb = {
121-
ciphertext: base64.encode(encryptedMetadataResult.payload.asString()),
122-
iv: base64.encode(keyInfo.unwrappedKeyIvBinary.asString()),
123-
};
124-
125-
const encryptedMetadataStr = JSON.stringify(encryptedMetadataOb);
126-
const keyAccessObject = await item.write(
127-
policy,
128-
unwrappedKeySplitBuffer,
129-
encryptedMetadataStr
130-
);
131-
keyAccessObjects.push(keyAccessObject);
132-
}
133-
134-
return keyAccessObjects;
135-
}
136-
137-
async generateIvBinary(): Promise<Binary> {
138-
const iv = await this.cipher.generateInitializationVector();
139-
return Binary.fromString(hex.decode(iv));
140-
}
141-
142-
async write(policy: Policy, keyInfo: KeyInfo): Promise<EncryptionInformation> {
143-
const algorithm = this.cipher?.name;
144-
if (!algorithm) {
145-
// Hard coded as part of the cipher object. This should not be reachable.
146-
throw new ConfigurationError('uninitialized cipher type');
147-
}
148-
const keyAccessObjects = await this.getKeyAccessObjects(policy, keyInfo);
149-
150-
// For now we're only concerned with a single (first) key access object
151-
const policyForManifest = base64.encode(JSON.stringify(policy));
152-
153-
return {
154-
type: 'split',
155-
keyAccess: keyAccessObjects,
156-
method: {
157-
algorithm,
158-
isStreamable: false,
159-
iv: base64.encode(keyInfo.unwrappedKeyIvBinary.asString()),
160-
},
161-
integrityInformation: {
162-
rootSignature: {
163-
alg: 'HS256',
164-
sig: '',
165-
},
166-
segmentHashAlg: 'GMAC',
167-
segments: [],
168-
},
169-
policy: policyForManifest,
170-
};
171-
}
172-
}

0 commit comments

Comments
 (0)