Skip to content

Commit

Permalink
feat: add cipher.getAuthTag. fix: cipher.setAuthTag. (margelo#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
shamilovtim committed Apr 13, 2024
1 parent 421bb51 commit 55f55c2
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 76 deletions.
30 changes: 27 additions & 3 deletions cpp/Cipher/MGLCipherHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,12 @@ void MGLCipherHostObject::installMethods() {
ok = EVP_CipherFinal_ex(ctx_, out.getBuffer(runtime).data(runtime),
&out_len) == 1;

// Additional operations for authenticated modes
if (ok && isCipher_ && IsAuthenticatedMode()) {
// In GCM mode, the authentication tag length can be specified in
// advance, but defaults to 16 bytes when encrypting. In CCM and OCB
// mode, it must always be given by the user.
// In GCM mode: default to 16 bytes.
// In CCM, OCB mode: must be provided by user.

// Logic for default auth tag length
if (auth_tag_len_ == kNoAuthTagLength) {
// TODO(osp) check
// CHECK(mode == EVP_CIPH_GCM_MODE);
Expand Down Expand Up @@ -446,6 +448,28 @@ void MGLCipherHostObject::installMethods() {
return EVP_CIPHER_CTX_set_padding(ctx_, arguments[0].getBool());
}));


// getAuthTag
this->fields.push_back(buildPair(
"getAuthTag", JSIF([this]) {
if (ctx_) {
throw jsi::JSError(runtime, "Cannot getAuthTag while encryption in progress.");
}
if (!isCipher_) {
throw jsi::JSError(runtime, "Cannot getAuthTag in decryption mode.");
}
if (auth_tag_len_ == kNoAuthTagLength) {
throw jsi::JSError(runtime, "Authentication tag not set or not available. Make sure to call 'final' before getting the authentication tag.");
}

MGLTypedArray<MGLTypedArrayKind::Uint8Array> authTagArray(runtime, auth_tag_len_);
auto buffer = authTagArray.getBuffer(runtime);
auto dataPtr = buffer.data(runtime);
std::memcpy(dataPtr, auth_tag_, auth_tag_len_);

return authTagArray;
}));

// setAuthTag
this->fields.push_back(buildPair(
"setAuthTag", JSIF([=]) {
Expand Down
59 changes: 35 additions & 24 deletions example/src/testing/Tests/CipherTests/CipherTestSecond.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,34 +72,45 @@ describe('createCipheriv/createDecipheriv', () => {
});
}

// function testCipher3(key: string, iv: string) {
// it('test3 + ' + key + ' + ' + iv, () => {
// // Test encryption and decryption with explicit key and iv.
// // AES Key Wrap test vector comes from RFC3394
// const plaintext = Buffer.from('00112233445566778899AABBCCDDEEFF', 'hex');

// const cipher = crypto.createCipheriv('id-aes128-wrap', key, iv);
// let ciph = cipher.update(plaintext, 'utf8', 'buffer');
// ciph = Buffer.concat([ciph, cipher.final('buffer')]);
// const ciph2 = Buffer.from(
// '1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5',
// 'hex'
// );
// assert(ciph.equals(ciph2));
// const decipher = crypto.createDecipheriv('id-aes128-wrap', key, iv);
// let deciph = decipher.update(ciph, 'buffer');
// deciph = Buffer.concat([deciph, decipher.final()]);

// assert(
// deciph.equals(plaintext),
// `encryption/decryption with key ${key} and iv ${iv}`
// );
// });
// }
function testAESGCM(key: Buffer, iv: Buffer) {
it('AES-GCM with key and iv ', () => {
const plaintext = 'Hello, world!';

// Encryption
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, {
// using an uncommon auth tag length for corner case checking. default is usually 16.
authTagLength: 4,
});
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
assert.strictEqual(Buffer.from(authTag).length, 4);

// Decryption
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv, {
// using an uncommon auth tag length for corner case checking. default is usually 16.
authTagLength: 4,
});
decipher.setAuthTag(Buffer.from(authTag));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');

assert.strictEqual(
decrypted,
plaintext,
'Decrypted text should match the original plaintext'
);
});
}

testCipher1('0123456789abcd0123456789', '12345678');
testCipher1('0123456789abcd0123456789', Buffer.from('12345678'));
testCipher1(Buffer.from('0123456789abcd0123456789'), '12345678');
testCipher1(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678'));
testCipher2(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678'));

// Key and IV generation for AES-GCM
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(12);
testAESGCM(key, iv);
});
23 changes: 22 additions & 1 deletion implementation-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@
This document attempts to describe the implementation status of Crypto APIs/Interfaces from Node.js in the `react-native-quick-crypto` library.

# `Crypto`
> TODO
> 🚧 needs to be filled out completely
## `encrypt`
| Algorithm | Status |
| --------- | :----: |
| `AES-CBC` | |
| `AES-CCM` | |
| `AES-CTR` | |
| `AES-GCM` ||
| `chacha20` | |
| `chacha20-poly1305` | |

## `decrypt`
| Algorithm | Status |
| --------- | :----: |
| `AES-CBC` | |
| `AES-CCM` | |
| `AES-CTR` | |
| `AES-GCM` ||
| `chacha20` | |
| `chacha20-poly1305` | |


# `SubtleCrypto`

Expand Down
62 changes: 14 additions & 48 deletions src/Cipher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,12 @@ class CipherCommon extends Stream.Transform {
return this;
}

// protected getAuthTag(): Buffer {
// return Buffer.from(this.internal.getAuthTag());
// }
public getAuthTag(): ArrayBuffer {
return this.internal.getAuthTag();
}

public setAuthTag(tag: Buffer): this {
this.internal.setAuthTag(tag.buffer);
this.internal.setAuthTag(binaryLikeToArrayBuffer(tag));
return this;
}
}
Expand Down Expand Up @@ -257,35 +257,19 @@ class Decipher extends CipherCommon {
export function createDecipher(
algorithm: string,
password: BinaryLike,
options?: Stream.TransformOptions
options?: Record<string, unknown> | Stream.TransformOptions
): Decipher {
return new Decipher(algorithm, password, options);
}

// TODO(osp) This definitions cause typescript errors when using the API
// export function createDecipheriv(
// algorithm: CipherCCMTypes,
// key: BinaryLike,
// iv: BinaryLike,
// options: CipherCCMOptions
// ): Decipher;
// export function createDecipheriv(
// algorithm: CipherOCBTypes,
// key: BinaryLike,
// iv: BinaryLike,
// options: CipherOCBOptions
// ): DecipherOCB;
// export function createDecipheriv(
// algorithm: CipherGCMTypes,
// key: BinaryLike,
// iv: BinaryLike,
// options?: CipherGCMOptions
// ): Decipher;
// function createDecipheriv(algorithm: CipherCCMTypes, key: CipherKey, iv: BinaryLike, options: CipherCCMOptions): DecipherCCM;
// function createDecipheriv(algorithm: CipherOCBTypes, key: CipherKey, iv: BinaryLike, options: CipherOCBOptions): DecipherOCB;
// function createDecipheriv(algorithm: CipherGCMTypes, key: CipherKey, iv: BinaryLike, options?: CipherGCMOptions): DecipherGCM;
export function createDecipheriv(
algorithm: string,
key: BinaryLike,
iv: BinaryLike | null,
options?: Stream.TransformOptions
options?: Record<string, unknown> | Stream.TransformOptions
): Decipher {
return new Decipher(algorithm, key, options, iv);
}
Expand All @@ -305,37 +289,19 @@ export function createDecipheriv(
export function createCipher(
algorithm: string,
password: BinaryLike,
options?: Stream.TransformOptions
options?: Record<string, unknown> | Stream.TransformOptions
): Cipher {
return new Cipher(algorithm, password, options);
}

// TODO(osp) on all the createCipheriv methods, node seems to use a "KeyObject" is seems to be a thread safe
// object that creates keys and what not. Not sure if we should support it.
// Fow now I replaced all of them to BinaryLike
// export function createCipheriv(
// algorithm: CipherCCMTypes,
// key: BinaryLike,
// iv: BinaryLike,
// options: CipherCCMOptions
// ): Cipher;
// export function createCipheriv(
// algorithm: CipherOCBTypes,
// key: BinaryLike,
// iv: BinaryLike,
// options: CipherOCBOptions
// ): CipherOCB;
// export function createCipheriv(
// algorithm: CipherGCMTypes,
// key: BinaryLike,
// iv: BinaryLike,
// options?: CipherGCMOptions
// ): Cipher;
// export function createCipheriv(algorithm: CipherCCMTypes, key: CipherKey, iv: BinaryLike, options: CipherCCMOptions): CipherCCM;
// export function createCipheriv(algorithm: CipherOCBTypes, key: CipherKey, iv: BinaryLike, options: CipherOCBOptions): CipherOCB;
// export function createCipheriv(algorithm: CipherGCMTypes, key: CipherKey, iv: BinaryLike, options?: CipherGCMOptions): CipherGCM;
export function createCipheriv(
algorithm: string,
key: BinaryLike,
iv: BinaryLike | null,
options?: Stream.TransformOptions
options?: Record<string, unknown> | Stream.TransformOptions
): Cipher {
return new Cipher(algorithm, key, options, iv);
}
Expand Down
1 change: 1 addition & 0 deletions src/NativeQuickCrypto/Cipher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type InternalCipher = {
}) => InternalCipher;
setAutoPadding: (autoPad: boolean) => boolean;
setAuthTag: (tag: ArrayBuffer) => boolean;
getAuthTag: () => ArrayBuffer;
};

export type CreateCipherMethod = (params: {
Expand Down

0 comments on commit 55f55c2

Please sign in to comment.