Changeset View
Standalone View
native/expo-modules/aes-crypto/ios/AESCryptoModule.swift
import ExpoModulesCore | import ExpoModulesCore | ||||||||||
import CryptoKit | import CryptoKit | ||||||||||
private let IV_LENGTH = 12 // bytes | private let IV_LENGTH = 12 // bytes | ||||||||||
private let TAG_LENGTH = 16 // bytes | private let TAG_LENGTH = 16 // bytes | ||||||||||
public class AESCryptoModule: Module { | public class AESCryptoModule: Module { | ||||||||||
public func definition() -> ModuleDefinition { | public func definition() -> ModuleDefinition { | ||||||||||
Name("AESCrypto") | Name("AESCrypto") | ||||||||||
Function("generateKey", generateKey) | Function("generateKey", generateKey) | ||||||||||
Function("encrypt", encrypt) | Function("encrypt", encrypt) | ||||||||||
Function("decrypt", decrypt) | |||||||||||
} | } | ||||||||||
} | } | ||||||||||
// MARK: - Function implementations | // MARK: - Function implementations | ||||||||||
private func generateKey(destination: Uint8Array) throws { | private func generateKey(destination: Uint8Array) throws { | ||||||||||
let key = SymmetricKey(size: .bits256) | let key = SymmetricKey(size: .bits256) | ||||||||||
guard destination.byteLength * 8 == key.bitCount else { | guard destination.byteLength * 8 == key.bitCount else { | ||||||||||
Show All 22 Lines | private func encrypt(rawKey: Uint8Array, | ||||||||||
// 'combined' returns concatenated: iv || ciphertext || tag | // 'combined' returns concatenated: iv || ciphertext || tag | ||||||||||
guard let ciphertext = encryptionResult.combined else { | guard let ciphertext = encryptionResult.combined else { | ||||||||||
// this happens only if Nonce/IV != 12 bytes long | // this happens only if Nonce/IV != 12 bytes long | ||||||||||
throw EncryptionFailedException("Incorrect AES configuration") | throw EncryptionFailedException("Incorrect AES configuration") | ||||||||||
} | } | ||||||||||
ciphertext.copyBytes(to: destination.rawBufferPtr()) | ciphertext.copyBytes(to: destination.rawBufferPtr()) | ||||||||||
} | } | ||||||||||
private func decrypt(rawKey: Uint8Array, | |||||||||||
ciphertext: Uint8Array, | |||||||||||
atul: Already mentioned in D7005, but it's kind of confusing how `ciphertext` can mean multiple… | |||||||||||
bartekAuthorUnsubmitted Done Inline ActionsDone bartek: Done | |||||||||||
destination: Uint8Array) throws { | |||||||||||
guard ciphertext.byteLength > IV_LENGTH + TAG_LENGTH, | |||||||||||
atulUnsubmitted Not Done Inline ActionsIt looks like this check handles one specific possible invalid ciphertext case. Not sure we need this, I'm fairly confident we can trust AES.GCM.SealedBox(...) to throw if ciphertext is invalid (this case + others)? Let me know if there's something I'm missing atul: It looks like this check handles one specific possible invalid `ciphertext` case.
Not sure we… | |||||||||||
bartekAuthorUnsubmitted Done Inline ActionsIf this check fails, we don't even need to create SealedBox because it's evident that the given input is too short. Anyway, I'll move this check to JS because we cannot even create a buffer with a negative length bartek: If this check fails, we don't even need to create `SealedBox` because it's evident that the… | |||||||||||
destination.byteLength >= ciphertext.byteLength - IV_LENGTH - TAG_LENGTH | |||||||||||
atulUnsubmitted Not Done Inline ActionsIs there a reason we're doing a >= check instead of ==? Seems like elsewhere we expect the sizes to be exact? atul: Is there a reason we're doing a `>=` check instead of `==`? Seems like elsewhere we expect the… | |||||||||||
else { | |||||||||||
throw InvalidDataLengthException() | |||||||||||
} | |||||||||||
let key = SymmetricKey(data: rawKey.data()) | |||||||||||
let ciphertextData = ciphertext.data() | |||||||||||
let iv = ciphertextData.prefix(IV_LENGTH) | |||||||||||
let tag = ciphertextData.suffix(TAG_LENGTH) | |||||||||||
let ciphertextContent = ciphertextData | |||||||||||
.dropFirst(IV_LENGTH) | |||||||||||
.dropLast(TAG_LENGTH) | |||||||||||
atulUnsubmitted Not Done Inline ActionsThis looks correct, but I'm hesitant about deconstructing cipherText "manually" based on offsets. This case is pretty simple, but still seems like a brittle approach. Is there a reason we can't use the init<D>(combined: D) throws where D : DataProtocol initializer instead? According to https://developer.apple.com/documentation/cryptokit/aes/gcm/sealedbox/init(combined:):
That sounds like exactly what we need? Is there something preventing us from using that initializer? atul: This looks correct, but I'm hesitant about deconstructing `cipherText` "manually" based on… | |||||||||||
bartekAuthorUnsubmitted Done Inline ActionsYes, this looks like what we want. I must have missed this initializer. I'll try and check if it works bartek: Yes, this looks like what we want. I must have missed this initializer. I'll try and check if… | |||||||||||
let sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: iv), | |||||||||||
atulUnsubmitted Not Done Inline Actions
Personal style preference would be to pull nonce out to a separate line. Totally arbitrary preference, feel free to go with whatever you prefer atul: //Personal// style preference would be to pull `nonce` out to a separate line.
Totally… | |||||||||||
ciphertext: ciphertextContent, | |||||||||||
tag: tag) | |||||||||||
let decryptedData = try AES.GCM.open(sealedBox, using: key) | |||||||||||
decryptedData.copyBytes(to: destination.rawBufferPtr()) | |||||||||||
} | |||||||||||
// MARK: - Utilities | // MARK: - Utilities | ||||||||||
extension TypedArray { | extension TypedArray { | ||||||||||
func data() -> Data { | func data() -> Data { | ||||||||||
Data(bytes: self.rawPointer, count: self.byteLength) | Data(bytes: self.rawPointer, count: self.byteLength) | ||||||||||
} | } | ||||||||||
func rawBufferPtr() -> UnsafeMutableRawBufferPointer { | func rawBufferPtr() -> UnsafeMutableRawBufferPointer { | ||||||||||
Show All 24 Lines |
Already mentioned in D7005, but it's kind of confusing how ciphertext can mean multiple things. I'm guessing here ciphertext is iv || ciphertext || tag concatenation vs. "just the ciphertext"?
Think some sort of seal(...)/sealedData (where sealedData = iv || ciphertext || tag concatenation) naming convention would make the distinction clearer?