암호화 저장소 도입 - study-pals/frontend GitHub Wiki
refresh token을 관리하기 위해, EncryptedStorage
처럼 키-값 쌍을 암호화하여 저장할 수 있는 방법이 필요하다.
이때 해당 방법은 Android, iOS, Windows, Macos 네 플랫폼에서 일관적으로 동작할 수 있어야 하며, 보안적인 결함이 존재하지 않아야 한다.
조사 결과 Android, iOS 환경에서만 동작한다.
공식 문서에 모바일 환경에서의 지원만이 명시되어 있으며, 관련 정보를 조사해본 결과 Android·iOS·Web까지만 네이티브 바인딩을 제공하고 있다.
따라서 Windows, Macos 환경을 커버할 수 없다.
react-native-async-storage
는 네 플랫폼에서 모두 동작한다. js 단에서 직접 암복호화를 구현하고, 이를 asyncStorage에 적용해보면 어떨까?
→ 암호화에 사용할 key값도 결국 어딘가에 저장해야 한다. key값을 네트워크 통신을 통해 서버에서 관리하는 게 아닌 한, 결국 key값을 위한 암호화된 저장소가 또 필요하다. 결국 문제는 원점으로 돌아간다.
Android, iOS 환경에서 암호화 저장소를 제공하며, Catalyst를 통해 macOS도 지원 가능하다.
https://mcodex.dev/react-native-sensitive-info/docs/
공식 문서 상으로는 네 가지 플랫폼을 모두 지원한다. 하지만 적용을 시도해 본 결과, Android와 Windows 둘 다에 적용 실패했다. 유지보수가 4년 전에 끊겨 더 이상 현재의 react native 아키텍처와 호환되지 않는 모양이다. 해당 레포지토리의 깃허브 이슈에도 관련하여 많은 이슈가 등록되어 있으나 해결되지 않고 있다.
플랫폼별로 암호화 저장소 로직을 분기시킨다.
- Android: keystore를 사용한다. (
react-native-keychain
으로 커버 가능) - iOS, Macos: keychain을 사용한다. (
react-native-keychain
으로 커버 가능) - Windows: 암호화 저장소 네이티브 모듈을 만들어 사용한다.
https://microsoft.github.io/react-native-windows/docs/native-platform-getting-started
대부분 공식 문서를 참고했다.
npx --yes [email protected] --react-native-version "0.78.2" rnw-secure-storage
- Library Type: Turbo module 선택
- Languages: Kotlin & Objective-C 선택
npx react-native init-windows --template cpp-lib --overwrite
// src/NativeRnwSecureStorage.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
get: (key: string) => Promise<string | null>;
set: (key: string, value: string) => Promise<void>;
delete: (key: string) => Promise<void>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('RnwSecureStorage');
codegen을 위해 package.json에 아래 config를 추가한다.
// package.json
{
"name": "rnw-secure-storage",
...
"codegenConfig": {
"name": "RnwSecureStorageSpec",
"type": "modules",
"jsSrcsDir": "src",
"includesGeneratedCode": true,
"windows": {
"namespace": "rnwSecureStorageCodegen",
"outputDirectory": "windows/rnwSecureStorage/codegen",
"separateDataTypes": true
},
...
이후 터미널에 npx react-native codegen-windows
를 실행하면 .clang-format, NativeRnwSecureStorageSpec.g.h 파일이 생성된다.
여기서 NativeRnwSecureStorageSpec.g.h는 JS와 통신할 API 정보를 가지는 구조체이다.
// NativeRnwSecureStorageSpec.g.h
struct RnwSecureStorageSpec : winrt::Microsoft::ReactNative::TurboModuleSpec {
static constexpr auto methods = std::tuple{
Method<void(std::string, Promise<std::optional<std::string>>) noexcept>{0, L"get"},
Method<void(std::string, std::string, Promise<void>) noexcept>{1, L"set"},
Method<void(std::string, Promise<void>) noexcept>{2, L"delete"},
};
rnwSecureStorage.cpp, rnwSecureStorage.h 파일에 실제 네이티브 모듈의 기능을 정의해야 한다. 이 부분은 AI의 도움을 받아 작성하였다.
Windows.Security.Credentials
API에서 제공하는 PasswordVault에 키-값 쌍을 저장하며, 내부적으로 DPAPI-AES 암호화를 사용한다.
tsc로 JS 코드를 번들링하고, rnw-secure-storage
라는 이름으로 npm에 배포하였다.
Internal error when a required entitlement isn't present.
iOS에서는 추가 설정 없이 잘 동작했으나, Macos에서 Keychain.setGenericPassword
호출 시 위 에러가 발생했다.
https://developer.apple.com/documentation/xcode/configuring-keychain-sharing
Xcode IDE 상에서 keychain sharing entitlement를 등록하여 문제를 해결하였다. 하지만 release 시에는 해당 Bundle Identifier를 사용하지 않으므로 추가로 문제가 발생할 여지가 있다. 배포 시 재점검할 필요가 있을 듯하다.