blob: 871fe0f2e7353890b6c0f5f0fb35a7b93c27c2b4 [file] [log] [blame]
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io' as io;
import 'package:file/file.dart';
import 'package:process/process.dart';
import 'log.dart';
import 'utils.dart';
/// Statuses reported by Apple's Notary Server.
///
/// See more:
/// * https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow
enum NotaryStatus {
pending,
failed,
succeeded,
}
/// Codesign and notarize all files within a [RemoteArchive].
class FileCodesignVisitor {
FileCodesignVisitor({
required this.commitHash,
required this.codesignCertName,
required this.codesignUserName,
required this.appSpecificPassword,
required this.codesignAppstoreId,
required this.codesignTeamId,
required this.codesignFilepaths,
required this.fileSystem,
required this.tempDir,
required this.processManager,
this.production = false,
});
/// Temp [Directory] to download/extract files to.
///
/// This file will be deleted if [validateAll] completes successfully.
final Directory tempDir;
final FileSystem fileSystem;
final ProcessManager processManager;
final String commitHash;
final String codesignCertName;
final String codesignUserName;
final String appSpecificPassword;
final String codesignAppstoreId;
final String codesignTeamId;
final bool production;
// TODO(xilaizhang): add back utitlity in later splits
Set<String> fileWithEntitlements = <String>{};
Set<String> fileWithoutEntitlements = <String>{};
Set<String> fileConsumed = <String>{};
Set<String> directoriesVisited = <String>{};
List<String> codesignFilepaths;
late final File entitlementsFile;
late final Directory remoteDownloadsDir;
late final Directory codesignedZipsDir;
int _remoteDownloadIndex = 0;
int get remoteDownloadIndex => _remoteDownloadIndex++;
static const String _entitlementsFileContents = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
''';
void _initialize() {
entitlementsFile = tempDir.childFile('Entitlements.plist')..writeAsStringSync(_entitlementsFileContents);
remoteDownloadsDir = tempDir.childDirectory('downloads')..createSync();
codesignedZipsDir = tempDir.childDirectory('codesigned_zips')..createSync();
}
/// The entrance point of examining and code signing an engine artifact.
Future<void> validateAll() async {
_initialize();
await Future<void>.value(null);
log.info('Codesigned all binaries in ${tempDir.path}');
await tempDir.delete(recursive: true);
}
/// Visit a [Directory] type while examining the file system extracted from an artifact.
Future<void> visitDirectory({
required Directory directory,
required String entitlementParentPath,
}) async {
log.info('Visiting directory ${directory.absolute.path}\n');
if (directoriesVisited.contains(directory.absolute.path)) {
log.warning(
'Warning! You are visiting a directory that has been visited before, the directory is ${directory.absolute.path}');
}
directoriesVisited.add(directory.absolute.path);
final List<FileSystemEntity> entities = await directory.list().toList();
for (FileSystemEntity entity in entities) {
if (entity is io.Directory) {
await visitDirectory(
directory: directory.childDirectory(entity.basename),
entitlementParentPath: entitlementParentPath,
);
continue;
}
final FileType childType = getFileType(
entity.absolute.path,
processManager,
);
if (childType == FileType.zip) {
await visitEmbeddedZip(
zipEntity: entity,
entitlementParentPath: entitlementParentPath,
);
}
log.info('Child file of direcotry ${directory.basename} is ${entity.basename}\n');
}
}
/// Unzip an [EmbeddedZip] and visit its children.
Future<void> visitEmbeddedZip({
required FileSystemEntity zipEntity,
required String entitlementParentPath,
}) async {
log.info('This embedded file is ${zipEntity.path} and entilementParentPath is $entitlementParentPath\n');
final String currentFileName = zipEntity.path.split('/').last;
final Directory newDir = tempDir.childDirectory('embedded_zip_${zipEntity.absolute.path.hashCode}');
await unzip(
inputZip: zipEntity,
outDir: newDir,
processManager: processManager,
);
// the virtual file path is advanced by the name of the embedded zip
final String currentZipEntitlementPath = '$entitlementParentPath/$currentFileName';
await visitDirectory(
directory: newDir,
entitlementParentPath: currentZipEntitlementPath,
);
await zipEntity.delete();
await zip(
inputDir: newDir,
outputZip: zipEntity,
processManager: processManager,
);
}
}