mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 19:24:01 +02:00
Harden Switch OTA install with TLS verification and reliable NRO replace.
Enable HTTPS peer checks via a romfs CA bundle, remove existing NROs before rename on sdmc, and surface launcher replace failures before writing version.txt.
This commit is contained in:
@@ -45,6 +45,7 @@ native/switch-ota-launcher/*.nacp
|
|||||||
native/switch-ota-launcher/*.elf
|
native/switch-ota-launcher/*.elf
|
||||||
native/switch-ota-launcher/*.map
|
native/switch-ota-launcher/*.map
|
||||||
native/switch-ota-launcher/romfs/logo.png
|
native/switch-ota-launcher/romfs/logo.png
|
||||||
|
native/switch-ota-launcher/romfs/cacert.pem
|
||||||
|
|
||||||
# Legacy manual convenience-copy location (superseded by /dist/android/)
|
# Legacy manual convenience-copy location (superseded by /dist/android/)
|
||||||
mobile/dist/
|
mobile/dist/
|
||||||
|
|||||||
@@ -135,9 +135,15 @@ endif
|
|||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
all: sync-romfs $(BUILD)
|
all: sync-romfs $(BUILD)
|
||||||
|
|
||||||
|
CACERT_URL := https://curl.se/ca/cacert.pem
|
||||||
|
CACERT_ROMFS := $(CURDIR)/$(ROMFS)/cacert.pem
|
||||||
|
|
||||||
sync-romfs:
|
sync-romfs:
|
||||||
@mkdir -p $(CURDIR)/$(ROMFS)
|
@mkdir -p $(CURDIR)/$(ROMFS)
|
||||||
@cp -f $(LOGO_SRC) $(CURDIR)/$(ROMFS)/logo.png
|
@cp -f $(LOGO_SRC) $(CURDIR)/$(ROMFS)/logo.png
|
||||||
|
@if ! curl -sfL --time-cond $(CACERT_ROMFS) -o $(CACERT_ROMFS) $(CACERT_URL); then \
|
||||||
|
[ -f $(CACERT_ROMFS) ] || (echo "sync-romfs: failed to fetch cacert.pem" && exit 1); \
|
||||||
|
fi
|
||||||
|
|
||||||
$(BUILD): sync-romfs
|
$(BUILD): sync-romfs
|
||||||
@[ -d $@ ] || mkdir -p $@
|
@[ -d $@ ] || mkdir -p $@
|
||||||
|
|||||||
@@ -69,5 +69,6 @@ Manifest: `scripts/switch/ota_launcher.manifest`.
|
|||||||
|
|
||||||
- Zip extraction uses `switch-zziplib` (`ota_unzip.c`) on device.
|
- Zip extraction uses `switch-zziplib` (`ota_unzip.c`) on device.
|
||||||
- OTA replaces launcher + game from the install zip (NACP versions stay aligned).
|
- OTA replaces launcher + game from the install zip (NACP versions stay aligned).
|
||||||
|
- HTTPS uses Mozilla CA bundle in romfs (`cacert.pem`, fetched at build time).
|
||||||
- Sphaira HOME forwarders cache metadata until reinstalled (see docs/switch-install.md).
|
- Sphaira HOME forwarders cache metadata until reinstalled (see docs/switch-install.md).
|
||||||
- Release runner: `switch-dev` + (`install_devkitpro_deps.sh` **or** Docker)
|
- Release runner: `switch-dev` + (`install_devkitpro_deps.sh` **or** Docker)
|
||||||
|
|||||||
@@ -185,14 +185,29 @@ static int run_update_flow(const char *install_dir) {
|
|||||||
}
|
}
|
||||||
remove(extracted);
|
remove(extracted);
|
||||||
|
|
||||||
|
int have_launcher = 0;
|
||||||
if (ota_unzip_extract_file(zip_path, LAUNCHER_MEMBER_IN_ZIP, extracted_launcher, err,
|
if (ota_unzip_extract_file(zip_path, LAUNCHER_MEMBER_IN_ZIP, extracted_launcher, err,
|
||||||
sizeof(err)) == 0 ||
|
sizeof(err)) == 0 ||
|
||||||
ota_unzip_extract_file(zip_path, OTA_LAUNCHER_NRO_NAME, extracted_launcher, err,
|
ota_unzip_extract_file(zip_path, OTA_LAUNCHER_NRO_NAME, extracted_launcher, err,
|
||||||
sizeof(err)) == 0) {
|
sizeof(err)) == 0) {
|
||||||
(void)ota_fs_atomic_replace_nro(install_dir, OTA_LAUNCHER_NRO_NAME, extracted_launcher, err,
|
have_launcher = 1;
|
||||||
sizeof(err));
|
|
||||||
remove(extracted_launcher);
|
|
||||||
}
|
}
|
||||||
|
if (!have_launcher) {
|
||||||
|
remove(zip_path);
|
||||||
|
show_update_error("Could not extract",
|
||||||
|
"Update zip is missing launcher files.", err, installed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (ota_fs_atomic_replace_nro(install_dir, OTA_LAUNCHER_NRO_NAME, extracted_launcher, err,
|
||||||
|
sizeof(err)) != 0) {
|
||||||
|
remove(extracted_launcher);
|
||||||
|
remove(zip_path);
|
||||||
|
show_update_error("Could not install",
|
||||||
|
"Game updated but launcher could not be replaced. Reinstall from the SD zip.",
|
||||||
|
err, installed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
remove(extracted_launcher);
|
||||||
|
|
||||||
char vpath[192];
|
char vpath[192];
|
||||||
snprintf(vpath, sizeof(vpath), "%s/version.txt", install_dir);
|
snprintf(vpath, sizeof(vpath), "%s/version.txt", install_dir);
|
||||||
|
|||||||
@@ -104,9 +104,8 @@ int ota_fs_atomic_replace_nro(const char *install_dir, const char *nro_name,
|
|||||||
if (err && err_len) snprintf(err, err_len, "copy to .part failed (%s)", nro_name);
|
if (err && err_len) snprintf(err, err_len, "copy to .part failed (%s)", nro_name);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#ifdef _WIN32
|
/* sdmc/FAT (Switch) and Windows do not replace an existing dest on rename. */
|
||||||
remove(dest);
|
remove(dest);
|
||||||
#endif
|
|
||||||
if (rename(part, dest) != 0) {
|
if (rename(part, dest) != 0) {
|
||||||
if (err && err_len) snprintf(err, err_len, "rename .part -> %s failed", nro_name);
|
if (err && err_len) snprintf(err, err_len, "rename .part -> %s failed", nro_name);
|
||||||
remove(part);
|
remove(part);
|
||||||
|
|||||||
@@ -10,6 +10,14 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__SWITCH__)
|
#if defined(__SWITCH__)
|
||||||
|
#define OTA_CA_BUNDLE "romfs:/cacert.pem"
|
||||||
|
|
||||||
|
static void ota_net_configure_tls(CURL *curl) {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CAINFO, OTA_CA_BUNDLE);
|
||||||
|
}
|
||||||
|
|
||||||
struct mem_buf {
|
struct mem_buf {
|
||||||
char *data;
|
char *data;
|
||||||
size_t len;
|
size_t len;
|
||||||
@@ -76,8 +84,7 @@ int ota_net_download_buffer(const char *url, long timeout_ms, char **out, size_t
|
|||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_ms);
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_ms);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_mem);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_mem);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &mem);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &mem);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
ota_net_configure_tls(curl);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
|
||||||
CURLcode rc = curl_easy_perform(curl);
|
CURLcode rc = curl_easy_perform(curl);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
if (rc != CURLE_OK) {
|
if (rc != CURLE_OK) {
|
||||||
@@ -122,8 +129,7 @@ int ota_net_download_file(const char *url, const char *path, long timeout_ms, ch
|
|||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_ms);
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_ms);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
ota_net_configure_tls(curl);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
|
||||||
if (progress) {
|
if (progress) {
|
||||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfer_progress);
|
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfer_progress);
|
||||||
|
|||||||
@@ -513,7 +513,11 @@ test('AC-010: Fonte do launcher e Makefile DEVKITPRO existem @spec:AC-010', () =
|
|||||||
assert.match(otaUi, /draw_text_wrapped_centered/);
|
assert.match(otaUi, /draw_text_wrapped_centered/);
|
||||||
assert.match(otaUi, /ota_ui_alert_error/);
|
assert.match(otaUi, /ota_ui_alert_error/);
|
||||||
assert.match(read('native/switch-ota-launcher/src/ota_net.c'), /XFERINFOFUNCTION|xfer_progress/);
|
assert.match(read('native/switch-ota-launcher/src/ota_net.c'), /XFERINFOFUNCTION|xfer_progress/);
|
||||||
|
assert.match(read('native/switch-ota-launcher/src/ota_net.c'), /CURLOPT_SSL_VERIFYPEER,\s*1L/);
|
||||||
|
assert.match(read('native/switch-ota-launcher/src/ota_net.c'), /romfs:\/cacert\.pem/);
|
||||||
|
assert.doesNotMatch(read('native/switch-ota-launcher/src/ota_net.c'), /CURLOPT_SSL_VERIFYPEER,\s*0L/);
|
||||||
assert.match(read('native/switch-ota-launcher/Makefile'), /^ROMFS\s*:=/m);
|
assert.match(read('native/switch-ota-launcher/Makefile'), /^ROMFS\s*:=/m);
|
||||||
|
assert.match(read('native/switch-ota-launcher/Makefile'), /cacert\.pem/);
|
||||||
|
|
||||||
const mk = read('native/switch-ota-launcher/Makefile');
|
const mk = read('native/switch-ota-launcher/Makefile');
|
||||||
assert.match(mk, /libnx\/switch_rules|DEVKITPRO/);
|
assert.match(mk, /libnx\/switch_rules|DEVKITPRO/);
|
||||||
@@ -526,7 +530,10 @@ test('AC-010: Fonte do launcher e Makefile DEVKITPRO existem @spec:AC-010', () =
|
|||||||
assert.match(read('native/switch-ota-launcher/src/main.c'), /ota_unzip_extract_file/);
|
assert.match(read('native/switch-ota-launcher/src/main.c'), /ota_unzip_extract_file/);
|
||||||
assert.match(read('native/switch-ota-launcher/src/main.c'), /GAME_MEMBER_IN_ZIP|switch\/gen1recomp\//);
|
assert.match(read('native/switch-ota-launcher/src/main.c'), /GAME_MEMBER_IN_ZIP|switch\/gen1recomp\//);
|
||||||
assert.match(read('native/switch-ota-launcher/src/main.c'), /LAUNCHER_MEMBER_IN_ZIP|ota_fs_atomic_replace_nro/);
|
assert.match(read('native/switch-ota-launcher/src/main.c'), /LAUNCHER_MEMBER_IN_ZIP|ota_fs_atomic_replace_nro/);
|
||||||
|
assert.match(read('native/switch-ota-launcher/src/main.c'), /ota_fs_atomic_replace_nro\([\s\S]*\) != 0/);
|
||||||
assert.match(read('native/switch-ota-launcher/src/ota_fs.c'), /ota_fs_atomic_replace_nro/);
|
assert.match(read('native/switch-ota-launcher/src/ota_fs.c'), /ota_fs_atomic_replace_nro/);
|
||||||
|
assert.match(read('native/switch-ota-launcher/src/ota_fs.c'), /remove\(dest\)/);
|
||||||
|
assert.doesNotMatch(read('native/switch-ota-launcher/src/ota_fs.c'), /#ifdef _WIN32[\s\S]*remove\(dest\)/);
|
||||||
assert.doesNotMatch(read('scripts/switch/install_devkitpro_deps.sh'), /switch-minizip/);
|
assert.doesNotMatch(read('scripts/switch/install_devkitpro_deps.sh'), /switch-minizip/);
|
||||||
assert.match(read('scripts/switch/install_devkitpro_deps.sh'), /switch-zziplib/);
|
assert.match(read('scripts/switch/install_devkitpro_deps.sh'), /switch-zziplib/);
|
||||||
assert.match(read('scripts/switch/install_devkitpro_deps.sh'), /switch-dev/);
|
assert.match(read('scripts/switch/install_devkitpro_deps.sh'), /switch-dev/);
|
||||||
|
|||||||
Reference in New Issue
Block a user