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

chore(docs): Describes encapsulation methods #433

Draft
wants to merge 4 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
148 changes: 148 additions & 0 deletions docs/kao-ec-wrapped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# How we use EC Encryption to encapsulate the DEK

## Overview

Our system uses hybrid Elliptic Curve (EC) encryption to encapsulate splits or shares of the Data Encryption Key (DEK).
We place each share in a Key Access Object (KAO), which includes metadata binding the fragment to a policy,
how the fragment can be used to reconstruct the DEK,
and information about how the fragment is encapsulated, such as the KAS URL.
This document explains the process and the underlying mechanisms involved.

## Terms

1. **Elliptic Curve Cryptography (ECC)**:
A public key cryptography approach based on the algebraic structure of elliptic curves over finite fields.
2. **Data Encryption Key (DEK)**:
A symmetric key used to encrypt the actual data.
3. **Key Access Object (KAO)**:
An object that stores information about how the DEK is stored and accessed.
4. **Ephemeral Key Pair**:
A temporary key pair generated for each encryption session.
5. **Key Agreement**:
A process to derive a shared secret between two parties using their private and public keys.

## Process

### 1. Initialization

When creating an ECWrapped KAO,
we generate an ephemeral key pair using the P-256 curve:

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

### 2. Key Agreement

To securely transmit the DEK,
we perform a key agreement between the ephemeral private key and the recipient's public key.
This derives a shared secret (KEK - Key Encryption Key):

```typescript
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

We then encrypt the DEK using the derived KEK with the AES-GCM algorithm.
We also generate a 12-byte initialization vector (IV) for this encryption.

```typescript
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);
```

### 4. Storing in KAO

We store the encrypted DEK (entityWrappedKey) along with other metadata in the KAO:

```typescript
const ephemeralPublicKeyPEM = await cryptoPublicToPem(ek.publicKey);
const kao: KeyAccessObject = {
type: 'ec-wrapped',
url: this.url,
protocol: 'kas',
wrappedKey: base64.encodeArrayBuffer(entityWrappedKey),
encryptedMetadata: base64.encodeArrayBuffer(encryptedMetadata),
policyBinding: {
alg: 'HS256',
hash: base64.encodeArrayBuffer(policyBinding),
},
schemaVersion,
ephemeralPublicKey: ephemeralPublicKeyPEM,
};
```

### 5. Decrypting Server Responses

Rewrap requests can indicate which algorithm they wish to use for the response
by the the server responds to the KAS rewrap requests,
we decrypt the response using key agreement and ECDH.
The `unwrapKey` method in `tdf.ts` handles this process.

We use the server's ephemeral public key 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',
});
```

We then decrypt the encrypted DEK 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,
we ensure that the DEK is securely transmitted and stored within the ECWrapped KAO.
This approach leverages the strength of ECC and the security of AES-GCM to protect the DEK from unauthorized access.
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
5 changes: 4 additions & 1 deletion lib/tdf3/src/tdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -674,20 +674,23 @@ async function unwrapKey({
const url = `${keySplitInfo.url}/v2/rewrap`;
let ephemeralEncryptionKeysRaw: AnyKeyPair;
let ephemeralEncryptionKeys: PemKeyPair;
let algorithm: string;
if (wrappingKeyAlgorithm === 'ec:secp256r1') {
ephemeralEncryptionKeysRaw = await generateKeyPair();
ephemeralEncryptionKeys = await cryptoService.cryptoToPemPair(ephemeralEncryptionKeysRaw);
algorithm = 'ES256';
} else if (wrappingKeyAlgorithm === 'rsa:2048' || !wrappingKeyAlgorithm) {
ephemeralEncryptionKeysRaw = await cryptoService.generateKeyPair();
ephemeralEncryptionKeys = await cryptoService.cryptoToPemPair(ephemeralEncryptionKeysRaw);
algorithm = 'RS256';
} else {
throw new ConfigurationError(`Unsupported wrapping key algorithm [${wrappingKeyAlgorithm}]`);
}

const clientPublicKey = ephemeralEncryptionKeys.publicKey;

const requestBodyStr = JSON.stringify({
algorithm: 'RS256',
algorithm,
keyAccess: keySplitInfo,
policy: manifest.encryptionInformation.policy,
clientPublicKey,
Expand Down
Loading