From f24ab4e984685d9efec389685d45b488cbae8dc4 Mon Sep 17 00:00:00 2001 From: fonkamloic Date: Fri, 12 Jun 2026 01:26:01 -0400 Subject: [PATCH] Tabled review follow-ups: key path derivation, rotation warning, CRLF-safe idempotency - _publicKeyYamlBlock prefers the public key next to the stored signing key path, covering keys generated with a custom --output-dir. - The preserve-existing-key branch in init now warns that a rotated-out key may have been preserved and points at keys register. - The keys-register idempotency check normalizes line endings before comparing. --- .../codepush_commands/_codepush_init.dart | 64 +++++++++++++------ .../codepush_commands/_codepush_keys.dart | 4 +- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/lib/src/commands/codepush_commands/_codepush_init.dart b/lib/src/commands/codepush_commands/_codepush_init.dart index 2275f87..45f5c44 100644 --- a/lib/src/commands/codepush_commands/_codepush_init.dart +++ b/lib/src/commands/codepush_commands/_codepush_init.dart @@ -143,7 +143,11 @@ class CodePushInitSubCommand extends Command { // ── Native setup ────────────────────────────────────────── final version = _readPubspecVersion() ?? '1.0.0+1'; _logger.info(''); - _setupAndroid(appId, version); + _setupAndroid( + appId, + version, + storedSigningKeyPath: await CodePushClient.getStoredSigningKey(), + ); _setupIos(version); _setupPubspec(); _logger.info(''); @@ -231,7 +235,11 @@ class CodePushInitSubCommand extends Command { // ── Android setup ───────────────────────────────────────────── - void _setupAndroid(String appId, String version) { + void _setupAndroid( + String appId, + String version, { + String? storedSigningKeyPath, + }) { final androidDir = Directory('android/app/src/main'); if (!androidDir.existsSync()) { _logger.detail('No android directory — skipping Android setup.'); @@ -246,7 +254,8 @@ class CodePushInitSubCommand extends Command { final assetsDir = Directory('${androidDir.path}/assets'); if (!assetsDir.existsSync()) assetsDir.createSync(recursive: true); final configFile = File('${assetsDir.path}/codepush.yaml'); - var keyBlock = _publicKeyYamlBlock(); + var keyBlock = + _publicKeyYamlBlock(storedSigningKeyPath: storedSigningKeyPath); if (keyBlock.isEmpty && configFile.existsSync()) { // No local key (CI, different machine, post-rotation) — keep a key // previously injected by `fcp codepush keys register` rather than @@ -256,7 +265,11 @@ class CodePushInitSubCommand extends Command { if (match != null) { keyBlock = match.group(0)!; if (!keyBlock.endsWith('\n')) keyBlock = '$keyBlock\n'; - _logger.detail('Preserved existing public_key in codepush.yaml'); + _logger.warn( + 'Preserved the existing public_key in codepush.yaml (no local ' + 'signing key found). If you rotated keys, run ' + '`fcp codepush keys register` to embed the new one.', + ); } } configFile.writeAsStringSync( @@ -405,24 +418,33 @@ $newCopyBlock /// signing public key exists, or an empty string otherwise. With a key /// in the config, devices require a valid patch signature; without one, /// only integrity checks run. - String _publicKeyYamlBlock() { - final publicKeyFile = - File('${F.homeDir()}/.flutter_codepush/codepush_public.pem'); - if (!publicKeyFile.existsSync()) return ''; - final String pem; - try { - pem = publicKeyFile.readAsStringSync().trim(); - } on FileSystemException catch (e) { - _logger.warn( - 'Could not read ${publicKeyFile.path}: $e — continuing without ' - 'embedding the public key.', - ); - return ''; + String _publicKeyYamlBlock({String? storedSigningKeyPath}) { + // Prefer the public key sitting next to the stored signing key (covers + // `keys generate --output-dir `), then the default location. + final candidates = [ + if (storedSigningKeyPath != null && storedSigningKeyPath.isNotEmpty) + '${File(storedSigningKeyPath).parent.path}/codepush_public.pem', + '${F.homeDir()}/.flutter_codepush/codepush_public.pem', + ]; + for (final candidate in candidates) { + final publicKeyFile = File(candidate); + if (!publicKeyFile.existsSync()) continue; + final String pem; + try { + pem = publicKeyFile.readAsStringSync().trim(); + } on FileSystemException catch (e) { + _logger.warn( + 'Could not read ${publicKeyFile.path}: $e — continuing without ' + 'embedding the public key.', + ); + continue; + } + if (pem.isEmpty) continue; + final indented = + pem.split('\n').map((line) => ' ${line.trim()}').join('\n'); + return 'public_key: |\n$indented\n'; } - if (pem.isEmpty) return ''; - final indented = - pem.split('\n').map((line) => ' ${line.trim()}').join('\n'); - return 'public_key: |\n$indented\n'; + return ''; } // ── iOS setup ───────────────────────────────────────────────── diff --git a/lib/src/commands/codepush_commands/_codepush_keys.dart b/lib/src/commands/codepush_commands/_codepush_keys.dart index 78aa126..6c0302c 100644 --- a/lib/src/commands/codepush_commands/_codepush_keys.dart +++ b/lib/src/commands/codepush_commands/_codepush_keys.dart @@ -268,7 +268,9 @@ class _KeysRegisterCommand extends Command { final indented = publicKeyPem.split('\n').map((line) => ' ${line.trim()}').join('\n'); final block = 'public_key: |\n$indented\n'; - if (content.contains(block)) return; // already up to date + if (content.replaceAll('\r\n', '\n').contains(block)) { + return; // already up to date (line endings normalized) + } final hadKey = kPublicKeyYamlBlockPattern.hasMatch(content); content = content.replaceAll(kPublicKeyYamlBlockPattern, ''); if (content.isNotEmpty && !content.endsWith('\n')) content += '\n';