signMacosPayload function

Future<void> signMacosPayload(
  1. MacosSignConfig config
)

Implementation

Future<void> signMacosPayload(MacosSignConfig config) async {
  if (!config.isAppBundle) {
    return signMacosExecutable(
      inputPath: config.inputPath,
      entitlementsPath: config.entitlementsPath,
      skipNotarize: config.skipNotarize,
      environment: config.environment,
    );
  }

  final env = config.environment ?? currentShipworldEnvironment;
  final identity = await _installSigningIdentity(env);
  try {
    final nested = <String>[];

    await for (final entity in Directory(
      config.inputPath,
    ).list(recursive: true, followLinks: false)) {
      final isNested = switch (entity) {
        File() => await _isMachO(entity),
        // Nested bundles seal their own resources, so signing the bundle
        // covers everything inside it.
        Directory() => _bundleExtensions.contains(p.extension(entity.path)),
        _ => false,
      };
      if (isNested) {
        nested.add(entity.path);
      }
    }

    // Deepest first, so an inner bundle is sealed before the one containing it.
    nested.sort((left, right) => right.length.compareTo(left.length));

    for (final path in nested) {
      await runChecked('codesign', [
        '--force',
        '--options',
        'runtime',
        '--sign',
        identity,
        path,
      ]);
    }

    await runChecked('codesign', [
      '--force',
      '--options',
      'runtime',
      '--entitlements',
      config.entitlementsPath,
      '--sign',
      identity,
      config.inputPath,
    ]);

    if (identity != '-' &&
        (env['APPLE_NOTARY_KEY_P8_BASE64']?.isNotEmpty ?? false) &&
        !config.skipNotarize) {
      // The notary service takes an archive, and stapling writes the ticket
      // into the bundle so Gatekeeper accepts it without network access.
      final zipPath = '${config.inputPath}.notarize.zip';
      await runChecked('ditto', [
        '-c',
        '-k',
        '--keepParent',
        config.inputPath,
        zipPath,
      ]);
      try {
        await _notarize(
          archivePath: zipPath,
          env: env,
          skipNotarize: config.skipNotarize,
        );
        await runChecked('xcrun', ['stapler', 'staple', config.inputPath]);
      } finally {
        await _deleteIfExists(zipPath);
      }
    } else {
      await _notarize(
        archivePath: config.inputPath,
        env: env,
        skipNotarize: config.skipNotarize,
      );
    }
  } finally {
    await _disposeSigningIdentity();
  }

  await runChecked('codesign', [
    '--verify',
    '--deep',
    '--strict',
    config.inputPath,
  ]);
}