Skip to content

Commit

Permalink
clarifications
Browse files Browse the repository at this point in the history
  • Loading branch information
dmihalcik-virtru committed Feb 20, 2025
1 parent c493c2c commit af6503e
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 39 deletions.
74 changes: 64 additions & 10 deletions docs/ec_encryption_for_dek.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ When an ECWrapped KAO is created,
an ephemeral key pair is generated using the P-256 curve:

```typescript
this.ephemeralKeyPair = crypto.subtle.generateKey(
const ephemera = crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256',
},
false,
['deriveBits', 'deriveKey']
/* extractable: */ false,
/* keyUsages: */ ['deriveBits', 'deriveKey']
);
```

Expand All @@ -44,16 +44,44 @@ a key agreement is performed between the ephemeral private key and the recipient
This derives a shared secret (KEK - Key Encryption Key):

```typescript
const kek = await keyAgreement(ek.privateKey, clientPublicKey, {
hkdfSalt: new TextEncoder().encode('salt'),
hkdfHash: 'SHA-256',
});
const kasPublicKey: CryptoKey = /* Fetch or otherwise load known KAS public key value */;
const sharedSecret = await crypto.subtle.deriveBits(
{
name: 'ECDH',
public: kasPublicKey,
},
ephemera.privateKey,
256
);
const ikm = await crypto.subtle.importKey(
'raw',
sharedSecret,
{
name: 'HKDF',
},
false,
['deriveKey']
);
const kek = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-256',
salt: new TextEncoder().encode('salt'),
},
ikm,
{
name: 'AES-GCM',
length: 256,
},
false,
['encrypt', 'decrypt']
);
```

### 3. Encryption

The DEK is then encrypted using the derived KEK with AES-GCM algorithm.
An initialization vector (IV) is also generated for this encryption:
A 12-byte initialization vector (IV) is also generated for this encryption.

```typescript
const iv = generateRandomNumber(12);
Expand All @@ -74,16 +102,42 @@ const kao: KeyAccessObject = {
url: this.url,
protocol: 'kas',
wrappedKey: base64.encodeArrayBuffer(entityWrappedKey),
encryptedMetadata: base64.encode(encryptedMetadataStr),
encryptedMetadata: base64.encodeArrayBuffer(encryptedMetadata),
policyBinding: {
alg: 'HS256',
hash: base64.encode(policyBinding),
hash: base64.encodeArrayBuffer(policyBinding),
},
schemaVersion,
ephemeralPublicKey: ephemeralPublicKeyPEM,
};
```

### 5. Decrypting Server Responses

When the server responds to the KAS rewrap requests,
the response is decrypted using key agreement and ECDH.
The `unwrapKey` method in `tdf.ts` handles this process.

The server's ephemeral public key is used to derive a shared secret (KEK) with the client's ephemeral private key:

```typescript
const serverEphemeralKey: CryptoKey = await pemPublicToCrypto(sessionPublicKey);
const ekr = ephemeralEncryptionKeysRaw as CryptoKeyPair;
const kek = await keyAgreement(ekr.privateKey, serverEphemeralKey, {
hkdfSalt: new TextEncoder().encode('salt'),
hkdfHash: 'SHA-256',
});
```

The encrypted DEK is then decrypted using the derived KEK and the initialization vector (IV) from the response:

```typescript
const wrappedKeyAndNonce = base64.decodeArrayBuffer(entityWrappedKey);
const iv = wrappedKeyAndNonce.slice(0, 12);
const wrappedKey = wrappedKeyAndNonce.slice(12);
const dek = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, kek, wrappedKey);
```

## Conclusion

By using EC encryption and ephemeral key pairs,
Expand Down
11 changes: 0 additions & 11 deletions lib/src/nanotdf-crypto/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,3 @@ export enum KeyType {
Private = 'private',
Public = 'public',
}

export enum KeyUsageType {
Encrypt = 'encrypt',
Decrypt = 'decrypt',
DeriveBits = 'deriveBits',
DeriveKey = 'deriveKey',
Verify = 'verify',
Sign = 'sign',
UnwrapKey = 'unwrapKey',
WrapKey = 'wrapKey',
}
10 changes: 6 additions & 4 deletions lib/src/nanotdf-crypto/generateKeyPair.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { AlgorithmName, NamedCurve, KeyUsageType } from './enums.js';
import { AlgorithmName, NamedCurve } from './enums.js';

interface GenerateKeyPairOptions {
type Subset<K, T extends K> = T;

type GenerateKeyPairOptions = {
type: AlgorithmName.ECDH | AlgorithmName.ECDSA;
curve: NamedCurve;
keyUsages: Array<KeyUsageType>;
keyUsages: Array<KeyUsage>;
isExtractable: boolean;
}

export async function generateKeyPair(
{ type: name, curve: namedCurve, keyUsages, isExtractable }: GenerateKeyPairOptions = {
type: AlgorithmName.ECDH,
curve: NamedCurve.P256,
keyUsages: [KeyUsageType.DeriveBits, KeyUsageType.DeriveKey],
keyUsages: ['deriveBits', 'deriveKey'],
isExtractable: true,
}
): Promise<CryptoKeyPair | never> {
Expand Down
18 changes: 8 additions & 10 deletions lib/src/nanotdf-crypto/keyAgreement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ export async function keyAgreement(
}

const {
bitLength = 256,
hkdfHash = HashType.Sha256,
hkdfInfo = new Uint8Array(),
hkdfSalt = new Uint8Array(),
keyCipher = CipherType.AesGcm,
Expand All @@ -107,33 +105,33 @@ export async function keyAgreement(
],
} = options;

const derivedBits = await crypto.subtle.deriveBits(
const sharedSecret = await crypto.subtle.deriveBits(
{
name: AlgorithmName.ECDH,
public: publicKey,
},
privateKey,
bitLength
256
);

const derivedKey = await crypto.subtle.importKey(
const ikm = await crypto.subtle.importKey(
KeyFormat.Raw,
derivedBits,
sharedSecret,
{
name: AlgorithmName.HKDF,
},
false,
[KEY_USAGE_DERIVE_KEY]
['deriveKey']
);

const symmetricKey = await crypto.subtle.deriveKey(
{
name: AlgorithmName.HKDF,
hash: hkdfHash,
name: 'HKDF',
hash: 'SHA-256',
salt: hkdfSalt,
info: hkdfInfo,
},
derivedKey,
ikm,
{
name: keyCipher,
length: keyLength,
Expand Down
4 changes: 2 additions & 2 deletions lib/src/nanotdf/encrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import getHkdfSalt from './helpers/getHkdfSalt.js';
import { getBitLength as authTagLengthForCipher } from './models/Ciphers.js';
import { TypedArray } from '../tdf/TypedArray.js';
import { GMAC_BINDING_LEN } from './constants.js';
import { AlgorithmName, KeyFormat, KeyUsageType } from './../nanotdf-crypto/enums.js';
import { AlgorithmName, KeyFormat } from './../nanotdf-crypto/enums.js';

import {
encrypt as cryptoEncrypt,
Expand Down Expand Up @@ -190,7 +190,7 @@ async function convertECDHToECDSA(key: CryptoKey, curveName: string): Promise<Cr
namedCurve: curveName,
},
true,
[KeyUsageType.Sign]
['sign']
);

return ecdsaPrivateKey;
Expand Down
4 changes: 2 additions & 2 deletions lib/tdf3/src/models/key-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ export class ECWrapped {
encryptedMetadataStr: string
): Promise<KeyAccessObject> {
const policyStr = JSON.stringify(policy);
const [ek, clientPublicKey] = await Promise.all([
const [ek, kasPublic] = await Promise.all([
this.ephemeralKeyPair,
pemPublicToCrypto(this.publicKey),
]);
const kek = await keyAgreement(ek.privateKey, clientPublicKey, {
const kek = await keyAgreement(ek.privateKey, kasPublic, {
hkdfSalt: new TextEncoder().encode('salt'),
hkdfHash: 'SHA-256',
});
Expand Down

0 comments on commit af6503e

Please sign in to comment.