Skip to content

Commit

Permalink
Support generating ECDH keys in TEE with access control
Browse files Browse the repository at this point in the history
Previously, the options `inTee` and `usageRequiresAuth` of
`generateKey()` were only supported for ECDSA keys. This commit extends
the support for those options to ECDH keys as well.

It also provides an example for deriving AES keys from ECDH keys and
encrypting/decrypting data with them, using the new options.

Boyscouting: reduce code duplication in the `crypto-sign.ts` snippet.
  • Loading branch information
cpetrov committed Dec 18, 2023
1 parent 4d97efe commit 6c2026a
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 55 deletions.
46 changes: 43 additions & 3 deletions snippets/crypto-derive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ const stack = Stack({stretch: true, spacing: 8, padding: 16, alignment: 'stre
tabris.onLog(({message}) => stack.append(TextView({text: message})));

(async () => {
await importAndDerive();
await generateDeriveEncryptAndDecrypt();
await generateDeriveEncryptAndDecrypt({inTee: true, usageRequiresAuth: true});
})().catch(console.error);

async function importAndDerive() {
const publicKeyBuffer = Uint8Array.of(
48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2,
1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3,
Expand Down Expand Up @@ -96,9 +101,44 @@ tabris.onLog(({message}) => stack.append(TextView({text: message})));
size * 8
);
}
}

async function generateDeriveEncryptAndDecrypt({inTee, usageRequiresAuth} = {inTee: false, usageRequiresAuth: false}) {
const ecdhP256 = {name: 'ECDH' as const, namedCurve: 'P-256' as const};
const aesGcm = {name: 'AES-GCM' as const};

// Generate Alice's ECDH key pair
const alicesKeyPair = await crypto.subtle.generateKey(ecdhP256, true, ['deriveBits']);

// Generate Bob's ECDH key pair
const bobsKeyPair = await crypto.subtle.generateKey(ecdhP256, true, ['deriveBits'], {inTee, usageRequiresAuth});

// Derive Alice's AES key
const alicesAesKey = await deriveAesKey(bobsKeyPair.publicKey, alicesKeyPair.privateKey, 'encrypt');

// Derive Bob's AES key
const bobsAesKey = await deriveAesKey(alicesKeyPair.publicKey, bobsKeyPair.privateKey, 'decrypt');

function test(name, actual, expected) {
console.log(name, expected === actual ? 'OK' : `expected: ${expected}, actual: ${actual}`);
// Encrypt a message with Alice's AES key
const message = await new Blob(['Message']).arrayBuffer();
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt({...aesGcm, iv}, alicesAesKey, message);

// Decrypt Alice's ciphertext with Bob's AES key
const plaintext = await crypto.subtle.decrypt({...aesGcm, iv}, bobsAesKey, ciphertext);

test('decrypted plaintext', byteArrayToString(plaintext), 'Message');

async function deriveAesKey(publicKey: CryptoKey, privateKey: CryptoKey, usage: 'encrypt' | 'decrypt') {
const derivedBits = await crypto.subtle.deriveBits({public: publicKey, ...ecdhP256}, privateKey, 256);
return await crypto.subtle.importKey('raw', derivedBits, aesGcm, true, [usage]);
}
}

})().catch(console.error);
function byteArrayToString(ab: ArrayBuffer) {
return String.fromCharCode.apply(null, new Uint8Array(ab));
}

function test(name, actual, expected) {
console.log(name, expected === actual ? 'OK' : `expected: ${expected}, actual: ${actual}`);
}
42 changes: 12 additions & 30 deletions snippets/crypto-sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,37 @@ tabris.onLog(({message}) => stack.append(TextView({text: message})));

(async function() {
await signAndVerify();
await signAndVerifyWithKeysInTeeRequiringAuth();
await signAndVerify({inTee: true, usageRequiresAuth: true});
}()).catch(console.error);

async function signAndVerify() {
async function signAndVerify({inTee, usageRequiresAuth} = {inTee: false, usageRequiresAuth: false}) {
console.log('ECDSA signing/verification with generated keys:');
const generationAlgorithm = {name: 'ECDSA' as const, namedCurve: 'P-256' as const};
const signingAlgorithm = {name: 'ECDSAinDERFormat' as const, hash: 'SHA-256' as const};

// Generate a key pair for signing and verifying
const keyPair = await crypto.subtle.generateKey(generationAlgorithm, true, ['sign', 'verify']);

// Export the public key and import it back
const publicKeySpki = await crypto.subtle.exportKey('spki', keyPair.publicKey);
const publicKey = await crypto.subtle.importKey('spki', publicKeySpki, generationAlgorithm, true, ['verify']);

// Sign a message
const message = await new Blob(['Message']).arrayBuffer();
const signature = await crypto.subtle.sign(signingAlgorithm, keyPair.privateKey, message);
console.log('Signature:', new Uint8Array(signature).join(', '));

// Verify the signature
const isValid = await crypto.subtle.verify(signingAlgorithm, publicKey, signature, message);
console.log('Signature valid:', isValid);
}

async function signAndVerifyWithKeysInTeeRequiringAuth() {
console.log('ECDSA signing/verification with keys generated in a trusted execution environment (TEE):');
const generationAlgorithm = {name: 'ECDSA' as const, namedCurve: 'P-256' as const};
const signingAlgorithm = {name: 'ECDSAinDERFormat' as const, hash: 'SHA-256' as const};

// Generate a key pair for signing and verifying
const keyPair = await crypto.subtle.generateKey(
generationAlgorithm,
true,
['sign', 'verify'],
{inTee: true, usageRequiresAuth: true}
{inTee, usageRequiresAuth}
);

// Export the private key and import it back
const privateKeyHandle = await crypto.subtle.exportKey('teeKeyHandle', keyPair.privateKey);
const algorithm = {name: 'ECDSA' as const, namedCurve: 'P-256' as const};
const privateKey = await crypto.subtle.importKey('teeKeyHandle', privateKeyHandle, algorithm, true, ['sign']);
let privateKeyImportedFromTee: CryptoKey;
if (inTee) {
// Export the private key and import it back
const privateKeyHandle = await crypto.subtle.exportKey('teeKeyHandle', keyPair.privateKey);
const alg = {name: 'ECDSA' as const, namedCurve: 'P-256' as const};
privateKeyImportedFromTee = await crypto.subtle.importKey('teeKeyHandle', privateKeyHandle, alg, true, ['sign']);
}

// Export the public key and import it back
const publicKeySpki = await crypto.subtle.exportKey('spki', keyPair.publicKey);
const publicKey = await crypto.subtle.importKey('spki', publicKeySpki, algorithm, true, ['verify']);
const publicKey = await crypto.subtle.importKey('spki', publicKeySpki, generationAlgorithm, true, ['verify']);

// Sign a message
const message = await new Blob(['Message']).arrayBuffer();
const privateKey = inTee ? privateKeyImportedFromTee : keyPair.privateKey;
const signature = await crypto.subtle.sign(signingAlgorithm, privateKey, message);
console.log('Signature:', new Uint8Array(signature).join(', '));

Expand Down
6 changes: 0 additions & 6 deletions src/tabris/Crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,6 @@ class SubtleCrypto {
if ('usageRequiresAuth' in options) {
checkType(options.usageRequiresAuth, Boolean, {name: 'options.usageRequiresAuth'});
}
if (options.inTee && algorithm.name !== 'ECDSA') {
throw new TypeError('options.inTee is only supported for ECDSA keys');
}
if (options.usageRequiresAuth && algorithm.name !== 'ECDSA') {
throw new TypeError('options.usageRequiresAuth is only supported for ECDSA keys');
}
if (options.usageRequiresAuth && !options.inTee) {
throw new TypeError('options.usageRequiresAuth is only supported for keys in TEE');
}
Expand Down
16 changes: 0 additions & 16 deletions test/tabris/Crypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1124,22 +1124,6 @@ describe('Crypto', function() {
expect(client.calls({op: 'create', type: 'tabris.CryptoKey'}).length).to.equal(0);
});

it('rejects options.inTee when algorithm name is not ECDSA', async function() {
params[0] = {name: 'ECDH', namedCurve: 'P-256'};
params[3] = {inTee: true};
await expect(generateKey())
.rejectedWith(TypeError, 'options.inTee is only supported for ECDSA keys');
expect(client.calls({op: 'create', type: 'tabris.CryptoKey'}).length).to.equal(0);
});

it('rejects options.usageRequiresAuth when algorithm name is not ECDSA', async function() {
params[0] = {name: 'ECDH', namedCurve: 'P-256'};
params[3] = {usageRequiresAuth: true};
await expect(generateKey())
.rejectedWith(TypeError, 'options.usageRequiresAuth is only supported for ECDSA keys');
expect(client.calls({op: 'create', type: 'tabris.CryptoKey'}).length).to.equal(0);
});

it('rejects options.usageRequiresAuth when options.inTee is not set', async function() {
params[3] = {usageRequiresAuth: true};
await expect(generateKey())
Expand Down

0 comments on commit 6c2026a

Please sign in to comment.