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:
Andrew Quenehen
2026-08-06 08:58:22 -03:00
parent c7e72149f1
commit 84e1a5177b
7 changed files with 44 additions and 9 deletions
+6
View File
@@ -135,9 +135,15 @@ endif
#---------------------------------------------------------------------------------
all: sync-romfs $(BUILD)
CACERT_URL := https://curl.se/ca/cacert.pem
CACERT_ROMFS := $(CURDIR)/$(ROMFS)/cacert.pem
sync-romfs:
@mkdir -p $(CURDIR)/$(ROMFS)
@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
@[ -d $@ ] || mkdir -p $@
+1
View File
@@ -69,5 +69,6 @@ Manifest: `scripts/switch/ota_launcher.manifest`.
- Zip extraction uses `switch-zziplib` (`ota_unzip.c`) on device.
- 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).
- Release runner: `switch-dev` + (`install_devkitpro_deps.sh` **or** Docker)
+18 -3
View File
@@ -185,14 +185,29 @@ static int run_update_flow(const char *install_dir) {
}
remove(extracted);
int have_launcher = 0;
if (ota_unzip_extract_file(zip_path, LAUNCHER_MEMBER_IN_ZIP, extracted_launcher, err,
sizeof(err)) == 0 ||
ota_unzip_extract_file(zip_path, OTA_LAUNCHER_NRO_NAME, extracted_launcher, err,
sizeof(err)) == 0) {
(void)ota_fs_atomic_replace_nro(install_dir, OTA_LAUNCHER_NRO_NAME, extracted_launcher, err,
sizeof(err));
remove(extracted_launcher);
have_launcher = 1;
}
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];
snprintf(vpath, sizeof(vpath), "%s/version.txt", install_dir);
+1 -2
View File
@@ -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);
return -1;
}
#ifdef _WIN32
/* sdmc/FAT (Switch) and Windows do not replace an existing dest on rename. */
remove(dest);
#endif
if (rename(part, dest) != 0) {
if (err && err_len) snprintf(err, err_len, "rename .part -> %s failed", nro_name);
remove(part);
+10 -4
View File
@@ -10,6 +10,14 @@
#endif
#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 {
char *data;
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_WRITEFUNCTION, write_mem);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &mem);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
ota_net_configure_tls(curl);
CURLcode rc = curl_easy_perform(curl);
curl_easy_cleanup(curl);
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_WRITEFUNCTION, write_file);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
ota_net_configure_tls(curl);
if (progress) {
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfer_progress);