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

Support generating ECDH keys in TEE with access control #2282

Merged
merged 1 commit into from
Dec 18, 2023
Merged
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
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);
cpetrov marked this conversation as resolved.
Show resolved Hide resolved

// Decrypt Alice's ciphertext with Bob's AES key
const plaintext = await crypto.subtle.decrypt({...aesGcm, iv}, bobsAesKey, ciphertext);
cpetrov marked this conversation as resolved.
Show resolved Hide resolved

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